diff options
author | Brandon Wiley <brandon@blanu.net> | 2016-11-15 14:52:04 -0600 |
---|---|---|
committer | Brandon Wiley <brandon@blanu.net> | 2016-11-15 14:52:04 -0600 |
commit | 8a7de7e1e252a73d786bafab042eedde8c025ad6 (patch) | |
tree | eefeca82da3a8f61bcc19ba2d4dd6c43952c77b7 /transports/obfs4 | |
parent | 93cb566c11b9c968677786bc6b5cf01ac06931e6 (diff) |
Removed transports from shapeshifter-dispatcher (now located in shapeshifter-transports)
Diffstat (limited to 'transports/obfs4')
-rw-r--r-- | transports/obfs4/framing/framing.go | 306 | ||||
-rw-r--r-- | transports/obfs4/framing/framing_test.go | 169 | ||||
-rw-r--r-- | transports/obfs4/handshake_ntor.go | 425 | ||||
-rw-r--r-- | transports/obfs4/handshake_ntor_test.go | 248 | ||||
-rw-r--r-- | transports/obfs4/obfs4.go | 646 | ||||
-rw-r--r-- | transports/obfs4/packet.go | 176 | ||||
-rw-r--r-- | transports/obfs4/statefile.go | 252 |
7 files changed, 0 insertions, 2222 deletions
diff --git a/transports/obfs4/framing/framing.go b/transports/obfs4/framing/framing.go deleted file mode 100644 index 9eaba69..0000000 --- a/transports/obfs4/framing/framing.go +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -// -// Package framing implements the obfs4 link framing and cryptography. -// -// The Encoder/Decoder shared secret format is: -// uint8_t[32] NaCl secretbox key -// uint8_t[16] NaCl Nonce prefix -// uint8_t[16] SipHash-2-4 key (used to obfsucate length) -// uint8_t[8] SipHash-2-4 IV -// -// The frame format is: -// uint16_t length (obfsucated, big endian) -// NaCl secretbox (Poly1305/XSalsa20) containing: -// uint8_t[16] tag (Part of the secretbox construct) -// uint8_t[] payload -// -// The length field is length of the NaCl secretbox XORed with the truncated -// SipHash-2-4 digest ran in OFB mode. -// -// Initialize K, IV[0] with values from the shared secret. -// On each packet, IV[n] = H(K, IV[n - 1]) -// mask[n] = IV[n][0:2] -// obfsLen = length ^ mask[n] -// -// The NaCl secretbox (Poly1305/XSalsa20) nonce format is: -// uint8_t[24] prefix (Fixed) -// uint64_t counter (Big endian) -// -// The counter is initialized to 1, and is incremented on each frame. Since -// the protocol is designed to be used over a reliable medium, the nonce is not -// transmitted over the wire as both sides of the conversation know the prefix -// and the initial counter value. It is imperative that the counter does not -// wrap, and sessions MUST terminate before 2^64 frames are sent. -// -package framing - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "io" - - "golang.org/x/crypto/nacl/secretbox" - - "github.com/OperatorFoundation/obfs4/common/csrand" - "github.com/OperatorFoundation/obfs4/common/drbg" -) - -const ( - // MaximumSegmentLength is the length of the largest possible segment - // including overhead. - MaximumSegmentLength = 1500 - (40 + 12) - - // FrameOverhead is the length of the framing overhead. - FrameOverhead = lengthLength + secretbox.Overhead - - // MaximumFramePayloadLength is the length of the maximum allowed payload - // per frame. - MaximumFramePayloadLength = MaximumSegmentLength - FrameOverhead - - // KeyLength is the length of the Encoder/Decoder secret key. - KeyLength = keyLength + noncePrefixLength + drbg.SeedLength - - maxFrameLength = MaximumSegmentLength - lengthLength - minFrameLength = FrameOverhead - lengthLength - - keyLength = 32 - - noncePrefixLength = 16 - nonceCounterLength = 8 - nonceLength = noncePrefixLength + nonceCounterLength - - lengthLength = 2 -) - -// Error returned when Decoder.Decode() requires more data to continue. -var ErrAgain = errors.New("framing: More data needed to decode") - -// Error returned when Decoder.Decode() failes to authenticate a frame. -var ErrTagMismatch = errors.New("framing: Poly1305 tag mismatch") - -// Error returned when the NaCl secretbox nonce's counter wraps (FATAL). -var ErrNonceCounterWrapped = errors.New("framing: Nonce counter wrapped") - -// InvalidPayloadLengthError is the error returned when Encoder.Encode() -// rejects the payload length. -type InvalidPayloadLengthError int - -func (e InvalidPayloadLengthError) Error() string { - return fmt.Sprintf("framing: Invalid payload length: %d", int(e)) -} - -type boxNonce struct { - prefix [noncePrefixLength]byte - counter uint64 -} - -func (nonce *boxNonce) init(prefix []byte) { - if noncePrefixLength != len(prefix) { - panic(fmt.Sprintf("BUG: Nonce prefix length invalid: %d", len(prefix))) - } - - copy(nonce.prefix[:], prefix) - nonce.counter = 1 -} - -func (nonce boxNonce) bytes(out *[nonceLength]byte) error { - // The security guarantee of Poly1305 is broken if a nonce is ever reused - // for a given key. Detect this by checking for counter wraparound since - // we start each counter at 1. If it ever happens that more than 2^64 - 1 - // frames are transmitted over a given connection, support for rekeying - // will be neccecary, but that's unlikely to happen. - if nonce.counter == 0 { - return ErrNonceCounterWrapped - } - - copy(out[:], nonce.prefix[:]) - binary.BigEndian.PutUint64(out[noncePrefixLength:], nonce.counter) - - return nil -} - -// Encoder is a frame encoder instance. -type Encoder struct { - key [keyLength]byte - nonce boxNonce - drbg *drbg.HashDrbg -} - -// NewEncoder creates a new Encoder instance. It must be supplied a slice -// containing exactly KeyLength bytes of keying material. -func NewEncoder(key []byte) *Encoder { - if len(key) != KeyLength { - panic(fmt.Sprintf("BUG: Invalid encoder key length: %d", len(key))) - } - - encoder := new(Encoder) - copy(encoder.key[:], key[0:keyLength]) - encoder.nonce.init(key[keyLength : keyLength+noncePrefixLength]) - seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:]) - if err != nil { - panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err)) - } - encoder.drbg, _ = drbg.NewHashDrbg(seed) - - return encoder -} - -// Encode encodes a single frame worth of payload and returns the encoded -// length. InvalidPayloadLengthError is recoverable, all other errors MUST be -// treated as fatal and the session aborted. -func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) { - payloadLen := len(payload) - if MaximumFramePayloadLength < payloadLen { - return 0, InvalidPayloadLengthError(payloadLen) - } - if len(frame) < payloadLen+FrameOverhead { - return 0, io.ErrShortBuffer - } - - // Generate a new nonce. - var nonce [nonceLength]byte - if err = encoder.nonce.bytes(&nonce); err != nil { - return 0, err - } - encoder.nonce.counter++ - - // Encrypt and MAC payload. - box := secretbox.Seal(frame[:lengthLength], payload, &nonce, &encoder.key) - - // Obfuscate the length. - length := uint16(len(box) - lengthLength) - lengthMask := encoder.drbg.NextBlock() - length ^= binary.BigEndian.Uint16(lengthMask) - binary.BigEndian.PutUint16(frame[:2], length) - - // Return the frame. - return len(box), nil -} - -// Decoder is a frame decoder instance. -type Decoder struct { - key [keyLength]byte - nonce boxNonce - drbg *drbg.HashDrbg - - nextNonce [nonceLength]byte - nextLength uint16 - nextLengthInvalid bool -} - -// NewDecoder creates a new Decoder instance. It must be supplied a slice -// containing exactly KeyLength bytes of keying material. -func NewDecoder(key []byte) *Decoder { - if len(key) != KeyLength { - panic(fmt.Sprintf("BUG: Invalid decoder key length: %d", len(key))) - } - - decoder := new(Decoder) - copy(decoder.key[:], key[0:keyLength]) - decoder.nonce.init(key[keyLength : keyLength+noncePrefixLength]) - seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:]) - if err != nil { - panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err)) - } - decoder.drbg, _ = drbg.NewHashDrbg(seed) - - return decoder -} - -// Decode decodes a stream of data and returns the length if any. ErrAgain is -// a temporary failure, all other errors MUST be treated as fatal and the -// session aborted. -func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) { - // A length of 0 indicates that we do not know how big the next frame is - // going to be. - if decoder.nextLength == 0 { - // Attempt to pull out the next frame length. - if lengthLength > frames.Len() { - return 0, ErrAgain - } - - // Remove the length field from the buffer. - var obfsLen [lengthLength]byte - _, err := io.ReadFull(frames, obfsLen[:]) - if err != nil { - return 0, err - } - - // Derive the nonce the peer used. - if err = decoder.nonce.bytes(&decoder.nextNonce); err != nil { - return 0, err - } - - // Deobfuscate the length field. - length := binary.BigEndian.Uint16(obfsLen[:]) - lengthMask := decoder.drbg.NextBlock() - length ^= binary.BigEndian.Uint16(lengthMask) - if maxFrameLength < length || minFrameLength > length { - // Per "Plaintext Recovery Attacks Against SSH" by - // Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson, - // there are a class of attacks againt protocols that use similar - // sorts of framing schemes. - // - // While obfs4 should not allow plaintext recovery (CBC mode is - // not used), attempt to mitigate out of bound frame length errors - // by pretending that the length was a random valid range as per - // the countermeasure suggested by Denis Bider in section 6 of the - // paper. - - decoder.nextLengthInvalid = true - length = uint16(csrand.IntRange(minFrameLength, maxFrameLength)) - } - decoder.nextLength = length - } - - if int(decoder.nextLength) > frames.Len() { - return 0, ErrAgain - } - - // Unseal the frame. - var box [maxFrameLength]byte - n, err := io.ReadFull(frames, box[:decoder.nextLength]) - if err != nil { - return 0, err - } - out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key) - if !ok || decoder.nextLengthInvalid { - // When a random length is used (on length error) the tag should always - // mismatch, but be paranoid. - return 0, ErrTagMismatch - } - - // Clean up and prepare for the next frame. - decoder.nextLength = 0 - decoder.nonce.counter++ - - return len(out), nil -} diff --git a/transports/obfs4/framing/framing_test.go b/transports/obfs4/framing/framing_test.go deleted file mode 100644 index 03e0d3b..0000000 --- a/transports/obfs4/framing/framing_test.go +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package framing - -import ( - "bytes" - "crypto/rand" - "testing" -) - -func generateRandomKey() []byte { - key := make([]byte, KeyLength) - - _, err := rand.Read(key) - if err != nil { - panic(err) - } - - return key -} - -func newEncoder(t *testing.T) *Encoder { - // Generate a key to use. - key := generateRandomKey() - - encoder := NewEncoder(key) - if encoder == nil { - t.Fatalf("NewEncoder returned nil") - } - - return encoder -} - -// TestNewEncoder tests the Encoder ctor. -func TestNewEncoder(t *testing.T) { - encoder := newEncoder(t) - _ = encoder -} - -// TestEncoder_Encode tests Encoder.Encode. -func TestEncoder_Encode(t *testing.T) { - encoder := newEncoder(t) - - buf := make([]byte, MaximumFramePayloadLength) - _, _ = rand.Read(buf) // YOLO - for i := 0; i <= MaximumFramePayloadLength; i++ { - var frame [MaximumSegmentLength]byte - n, err := encoder.Encode(frame[:], buf[0:i]) - if err != nil { - t.Fatalf("Encoder.encode([%d]byte), failed: %s", i, err) - } - if n != i+FrameOverhead { - t.Fatalf("Unexpected encoded framesize: %d, expecting %d", n, i+ - FrameOverhead) - } - } -} - -// TestEncoder_Encode_Oversize tests oversized frame rejection. -func TestEncoder_Encode_Oversize(t *testing.T) { - encoder := newEncoder(t) - - var frame [MaximumSegmentLength]byte - var buf [MaximumFramePayloadLength + 1]byte - _, _ = rand.Read(buf[:]) // YOLO - _, err := encoder.Encode(frame[:], buf[:]) - if _, ok := err.(InvalidPayloadLengthError); !ok { - t.Error("Encoder.encode() returned unexpected error:", err) - } -} - -// TestNewDecoder tests the Decoder ctor. -func TestNewDecoder(t *testing.T) { - key := generateRandomKey() - decoder := NewDecoder(key) - if decoder == nil { - t.Fatalf("NewDecoder returned nil") - } -} - -// TestDecoder_Decode tests Decoder.Decode. -func TestDecoder_Decode(t *testing.T) { - key := generateRandomKey() - - encoder := NewEncoder(key) - decoder := NewDecoder(key) - - var buf [MaximumFramePayloadLength]byte - _, _ = rand.Read(buf[:]) // YOLO - for i := 0; i <= MaximumFramePayloadLength; i++ { - var frame [MaximumSegmentLength]byte - encLen, err := encoder.Encode(frame[:], buf[0:i]) - if err != nil { - t.Fatalf("Encoder.encode([%d]byte), failed: %s", i, err) - } - if encLen != i+FrameOverhead { - t.Fatalf("Unexpected encoded framesize: %d, expecting %d", encLen, - i+FrameOverhead) - } - - var decoded [MaximumFramePayloadLength]byte - - decLen, err := decoder.Decode(decoded[:], bytes.NewBuffer(frame[:encLen])) - if err != nil { - t.Fatalf("Decoder.decode([%d]byte), failed: %s", i, err) - } - if decLen != i { - t.Fatalf("Unexpected decoded framesize: %d, expecting %d", - decLen, i) - } - - if 0 != bytes.Compare(decoded[:decLen], buf[:i]) { - t.Fatalf("Frame %d does not match encoder input", i) - } - } -} - -// BencharkEncoder_Encode benchmarks Encoder.Encode processing 1 MiB -// of payload. -func BenchmarkEncoder_Encode(b *testing.B) { - var chopBuf [MaximumFramePayloadLength]byte - var frame [MaximumSegmentLength]byte - payload := make([]byte, 1024*1024) - encoder := NewEncoder(generateRandomKey()) - b.ResetTimer() - - for i := 0; i < b.N; i++ { - transfered := 0 - buffer := bytes.NewBuffer(payload) - for 0 < buffer.Len() { - n, err := buffer.Read(chopBuf[:]) - if err != nil { - b.Fatal("buffer.Read() failed:", err) - } - - n, err = encoder.Encode(frame[:], chopBuf[:n]) - transfered += n - FrameOverhead - } - if transfered != len(payload) { - b.Fatalf("Transfered length mismatch: %d != %d", transfered, - len(payload)) - } - } -} diff --git a/transports/obfs4/handshake_ntor.go b/transports/obfs4/handshake_ntor.go deleted file mode 100644 index ed7c114..0000000 --- a/transports/obfs4/handshake_ntor.go +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package obfs4 - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/hex" - "errors" - "fmt" - "hash" - "strconv" - "time" - - "github.com/OperatorFoundation/obfs4/common/csrand" - "github.com/OperatorFoundation/obfs4/common/ntor" - "github.com/OperatorFoundation/obfs4/common/replayfilter" - "github.com/OperatorFoundation/obfs4/transports/obfs4/framing" -) - -const ( - maxHandshakeLength = 8192 - - clientMinPadLength = (serverMinHandshakeLength + inlineSeedFrameLength) - - clientMinHandshakeLength - clientMaxPadLength = maxHandshakeLength - clientMinHandshakeLength - clientMinHandshakeLength = ntor.RepresentativeLength + markLength + macLength - - serverMinPadLength = 0 - serverMaxPadLength = maxHandshakeLength - (serverMinHandshakeLength + - inlineSeedFrameLength) - serverMinHandshakeLength = ntor.RepresentativeLength + ntor.AuthLength + - markLength + macLength - - markLength = sha256.Size / 2 - macLength = sha256.Size / 2 - - inlineSeedFrameLength = framing.FrameOverhead + packetOverhead + seedPacketPayloadLength -) - -// ErrMarkNotFoundYet is the error returned when the obfs4 handshake is -// incomplete and requires more data to continue. This error is non-fatal and -// is the equivalent to EAGAIN/EWOULDBLOCK. -var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet") - -// ErrInvalidHandshake is the error returned when the obfs4 handshake fails due -// to the peer not sending the correct mark. This error is fatal and the -// connection MUST be dropped. -var ErrInvalidHandshake = errors.New("handshake: Failed to find M_[C,S]") - -// ErrReplayedHandshake is the error returned when the obfs4 handshake fails -// due it being replayed. This error is fatal and the connection MUST be -// dropped. -var ErrReplayedHandshake = errors.New("handshake: Replay detected") - -// ErrNtorFailed is the error returned when the ntor handshake fails. This -// error is fatal and the connection MUST be dropped. -var ErrNtorFailed = errors.New("handshake: ntor handshake failure") - -// InvalidMacError is the error returned when the handshake MACs do not match. -// This error is fatal and the connection MUST be dropped. -type InvalidMacError struct { - Derived []byte - Received []byte -} - -func (e *InvalidMacError) Error() string { - return fmt.Sprintf("handshake: MAC mismatch: Dervied: %s Received: %s.", - hex.EncodeToString(e.Derived), hex.EncodeToString(e.Received)) -} - -// InvalidAuthError is the error returned when the ntor AUTH tags do not match. -// This error is fatal and the connection MUST be dropped. -type InvalidAuthError struct { - Derived *ntor.Auth - Received *ntor.Auth -} - -func (e *InvalidAuthError) Error() string { - return fmt.Sprintf("handshake: ntor AUTH mismatch: Derived: %s Received:%s.", - hex.EncodeToString(e.Derived.Bytes()[:]), - hex.EncodeToString(e.Received.Bytes()[:])) -} - -type clientHandshake struct { - keypair *ntor.Keypair - nodeID *ntor.NodeID - serverIdentity *ntor.PublicKey - epochHour []byte - - padLen int - mac hash.Hash - - serverRepresentative *ntor.Representative - serverAuth *ntor.Auth - serverMark []byte -} - -func newClientHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.PublicKey, sessionKey *ntor.Keypair) *clientHandshake { - hs := new(clientHandshake) - hs.keypair = sessionKey - hs.nodeID = nodeID - hs.serverIdentity = serverIdentity - hs.padLen = csrand.IntRange(clientMinPadLength, clientMaxPadLength) - hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Bytes()[:], hs.nodeID.Bytes()[:]...)) - - return hs -} - -func (hs *clientHandshake) generateHandshake() ([]byte, error) { - var buf bytes.Buffer - - hs.mac.Reset() - hs.mac.Write(hs.keypair.Representative().Bytes()[:]) - mark := hs.mac.Sum(nil)[:markLength] - - // The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) where: - // * X is the client's ephemeral Curve25519 public key representative. - // * P_C is [clientMinPadLength,clientMaxPadLength] bytes of random padding. - // * M_C is HMAC-SHA256-128(serverIdentity | NodeID, X) - // * MAC is HMAC-SHA256-128(serverIdentity | NodeID, X .... E) - // * E is the string representation of the number of hours since the UNIX - // epoch. - - // Generate the padding - pad, err := makePad(hs.padLen) - if err != nil { - return nil, err - } - - // Write X, P_C, M_C. - buf.Write(hs.keypair.Representative().Bytes()[:]) - buf.Write(pad) - buf.Write(mark) - - // Calculate and write the MAC. - hs.mac.Reset() - hs.mac.Write(buf.Bytes()) - hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10)) - hs.mac.Write(hs.epochHour) - buf.Write(hs.mac.Sum(nil)[:macLength]) - - return buf.Bytes(), nil -} - -func (hs *clientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) { - // No point in examining the data unless the miminum plausible response has - // been received. - if serverMinHandshakeLength > len(resp) { - return 0, nil, ErrMarkNotFoundYet - } - - if hs.serverRepresentative == nil || hs.serverAuth == nil { - // Pull out the representative/AUTH. (XXX: Add ctors to ntor) - hs.serverRepresentative = new(ntor.Representative) - copy(hs.serverRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength]) - hs.serverAuth = new(ntor.Auth) - copy(hs.serverAuth.Bytes()[:], resp[ntor.RepresentativeLength:]) - - // Derive the mark. - hs.mac.Reset() - hs.mac.Write(hs.serverRepresentative.Bytes()[:]) - hs.serverMark = hs.mac.Sum(nil)[:markLength] - } - - // Attempt to find the mark + MAC. - pos := findMarkMac(hs.serverMark, resp, ntor.RepresentativeLength+ntor.AuthLength+serverMinPadLength, - maxHandshakeLength, false) - if pos == -1 { - if len(resp) >= maxHandshakeLength { - return 0, nil, ErrInvalidHandshake - } - return 0, nil, ErrMarkNotFoundYet - } - - // Validate the MAC. - hs.mac.Reset() - hs.mac.Write(resp[:pos+markLength]) - hs.mac.Write(hs.epochHour) - macCmp := hs.mac.Sum(nil)[:macLength] - macRx := resp[pos+markLength : pos+markLength+macLength] - if !hmac.Equal(macCmp, macRx) { - return 0, nil, &InvalidMacError{macCmp, macRx} - } - - // Complete the handshake. - serverPublic := hs.serverRepresentative.ToPublic() - ok, seed, auth := ntor.ClientHandshake(hs.keypair, serverPublic, - hs.serverIdentity, hs.nodeID) - if !ok { - return 0, nil, ErrNtorFailed - } - if !ntor.CompareAuth(auth, hs.serverAuth.Bytes()[:]) { - return 0, nil, &InvalidAuthError{auth, hs.serverAuth} - } - - return pos + markLength + macLength, seed.Bytes()[:], nil -} - -type serverHandshake struct { - keypair *ntor.Keypair - nodeID *ntor.NodeID - serverIdentity *ntor.Keypair - epochHour []byte - serverAuth *ntor.Auth - - padLen int - mac hash.Hash - - clientRepresentative *ntor.Representative - clientMark []byte -} - -func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessionKey *ntor.Keypair) *serverHandshake { - hs := new(serverHandshake) - hs.keypair = sessionKey - hs.nodeID = nodeID - hs.serverIdentity = serverIdentity - hs.padLen = csrand.IntRange(serverMinPadLength, serverMaxPadLength) - hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Public().Bytes()[:], hs.nodeID.Bytes()[:]...)) - - return hs -} - -func (hs *serverHandshake) parseClientHandshake(filter *replayfilter.ReplayFilter, resp []byte) ([]byte, error) { - // No point in examining the data unless the miminum plausible response has - // been received. - if clientMinHandshakeLength > len(resp) { - return nil, ErrMarkNotFoundYet - } - - if hs.clientRepresentative == nil { - // Pull out the representative/AUTH. (XXX: Add ctors to ntor) - hs.clientRepresentative = new(ntor.Representative) - copy(hs.clientRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength]) - - // Derive the mark. - hs.mac.Reset() - hs.mac.Write(hs.clientRepresentative.Bytes()[:]) - hs.clientMark = hs.mac.Sum(nil)[:markLength] - } - - // Attempt to find the mark + MAC. - pos := findMarkMac(hs.clientMark, resp, ntor.RepresentativeLength+clientMinPadLength, - maxHandshakeLength, true) - if pos == -1 { - if len(resp) >= maxHandshakeLength { - return nil, ErrInvalidHandshake - } - return nil, ErrMarkNotFoundYet - } - - // Validate the MAC. - macFound := false - for _, off := range []int64{0, -1, 1} { - // Allow epoch to be off by up to a hour in either direction. - epochHour := []byte(strconv.FormatInt(getEpochHour()+int64(off), 10)) - hs.mac.Reset() - hs.mac.Write(resp[:pos+markLength]) - hs.mac.Write(epochHour) - macCmp := hs.mac.Sum(nil)[:macLength] - macRx := resp[pos+markLength : pos+markLength+macLength] - if hmac.Equal(macCmp, macRx) { - // Ensure that this handshake has not been seen previously. - if filter.TestAndSet(time.Now(), macRx) { - // The client either happened to generate exactly the same - // session key and padding, or someone is replaying a previous - // handshake. In either case, fuck them. - return nil, ErrReplayedHandshake - } - - macFound = true - hs.epochHour = epochHour - - // We could break out here, but in the name of reducing timing - // variation, evaluate all 3 MACs. - } - } - if !macFound { - // This probably should be an InvalidMacError, but conveying the 3 MACS - // that would be accepted is annoying so just return a generic fatal - // failure. - return nil, ErrInvalidHandshake - } - - // Client should never sent trailing garbage. - if len(resp) != pos+markLength+macLength { - return nil, ErrInvalidHandshake - } - - clientPublic := hs.clientRepresentative.ToPublic() - ok, seed, auth := ntor.ServerHandshake(clientPublic, hs.keypair, - hs.serverIdentity, hs.nodeID) - if !ok { - return nil, ErrNtorFailed - } - hs.serverAuth = auth - - return seed.Bytes()[:], nil -} - -func (hs *serverHandshake) generateHandshake() ([]byte, error) { - var buf bytes.Buffer - - hs.mac.Reset() - hs.mac.Write(hs.keypair.Representative().Bytes()[:]) - mark := hs.mac.Sum(nil)[:markLength] - - // The server handshake is Y | AUTH | P_S | M_S | MAC(Y | AUTH | P_S | M_S | E) where: - // * Y is the server's ephemeral Curve25519 public key representative. - // * AUTH is the ntor handshake AUTH value. - // * P_S is [serverMinPadLength,serverMaxPadLength] bytes of random padding. - // * M_S is HMAC-SHA256-128(serverIdentity | NodeID, Y) - // * MAC is HMAC-SHA256-128(serverIdentity | NodeID, Y .... E) - // * E is the string representation of the number of hours since the UNIX - // epoch. - - // Generate the padding - pad, err := makePad(hs.padLen) - if err != nil { - return nil, err - } - - // Write Y, AUTH, P_S, M_S. - buf.Write(hs.keypair.Representative().Bytes()[:]) - buf.Write(hs.serverAuth.Bytes()[:]) - buf.Write(pad) - buf.Write(mark) - - // Calculate and write the MAC. - hs.mac.Reset() - hs.mac.Write(buf.Bytes()) - hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10)) - hs.mac.Write(hs.epochHour) - buf.Write(hs.mac.Sum(nil)[:macLength]) - - return buf.Bytes(), nil -} - -// getEpochHour returns the number of hours since the UNIX epoch. -func getEpochHour() int64 { - return time.Now().Unix() / 3600 -} - -func findMarkMac(mark, buf []byte, startPos, maxPos int, fromTail bool) (pos int) { - if len(mark) != markLength { - panic(fmt.Sprintf("BUG: Invalid mark length: %d", len(mark))) - } - - endPos := len(buf) - if startPos > len(buf) { - return -1 - } - if endPos > maxPos { - endPos = maxPos - } - if endPos-startPos < markLength+macLength { - return -1 - } - - if fromTail { - // The server can optimize the search process by only examining the - // tail of the buffer. The client can't send valid data past M_C | - // MAC_C as it does not have the server's public key yet. - pos = endPos - (markLength + macLength) - if !hmac.Equal(buf[pos:pos+markLength], mark) { - return -1 - } - - return - } - - // The client has to actually do a substring search since the server can - // and will send payload trailing the response. - // - // XXX: bytes.Index() uses a naive search, which kind of sucks. - pos = bytes.Index(buf[startPos:endPos], mark) - if pos == -1 { - return -1 - } - - // Ensure that there is enough trailing data for the MAC. - if startPos+pos+markLength+macLength > endPos { - return -1 - } - - // Return the index relative to the start of the slice. - pos += startPos - return -} - -func makePad(padLen int) ([]byte, error) { - pad := make([]byte, padLen) - if err := csrand.Bytes(pad); err != nil { - return nil, err - } - - return pad, nil -} diff --git a/transports/obfs4/handshake_ntor_test.go b/transports/obfs4/handshake_ntor_test.go deleted file mode 100644 index 04afcef..0000000 --- a/transports/obfs4/handshake_ntor_test.go +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package obfs4 - -import ( - "bytes" - "testing" - - "github.com/OperatorFoundation/obfs4/common/ntor" - "github.com/OperatorFoundation/obfs4/common/replayfilter" -) - -func TestHandshakeNtorClient(t *testing.T) { - // Generate the server node id and id keypair, and ephemeral session keys. - nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13")) - idKeypair, _ := ntor.NewKeypair(false) - serverFilter, _ := replayfilter.New(replayTTL) - clientKeypair, err := ntor.NewKeypair(true) - if err != nil { - t.Fatalf("client: ntor.NewKeypair failed: %s", err) - } - serverKeypair, err := ntor.NewKeypair(true) - if err != nil { - t.Fatalf("server: ntor.NewKeypair failed: %s", err) - } - - // Test client handshake padding. - for l := clientMinPadLength; l <= clientMaxPadLength; l++ { - // Generate the client state and override the pad length. - clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) - clientHs.padLen = l - - // Generate what the client will send to the server. - clientBlob, err := clientHs.generateHandshake() - if err != nil { - t.Fatalf("[%d:0] clientHandshake.generateHandshake() failed: %s", l, err) - } - if len(clientBlob) > maxHandshakeLength { - t.Fatalf("[%d:0] Generated client body is oversized: %d", l, len(clientBlob)) - } - if len(clientBlob) < clientMinHandshakeLength { - t.Fatalf("[%d:0] Generated client body is undersized: %d", l, len(clientBlob)) - } - if len(clientBlob) != clientMinHandshakeLength+l { - t.Fatalf("[%d:0] Generated client body incorrect size: %d", l, len(clientBlob)) - } - - // Generate the server state and override the pad length. - serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) - serverHs.padLen = serverMinPadLength - - // Parse the client handshake message. - serverSeed, err := serverHs.parseClientHandshake(serverFilter, clientBlob) - if err != nil { - t.Fatalf("[%d:0] serverHandshake.parseClientHandshake() failed: %s", l, err) - } - - // Genrate what the server will send to the client. - serverBlob, err := serverHs.generateHandshake() - if err != nil { - t.Fatalf("[%d:0]: serverHandshake.generateHandshake() failed: %s", l, err) - } - - // Parse the server handshake message. - clientHs.serverRepresentative = nil - n, clientSeed, err := clientHs.parseServerHandshake(serverBlob) - if err != nil { - t.Fatalf("[%d:0] clientHandshake.parseServerHandshake() failed: %s", l, err) - } - if n != len(serverBlob) { - t.Fatalf("[%d:0] clientHandshake.parseServerHandshake() has bytes remaining: %d", l, n) - } - - // Ensure the derived shared secret is the same. - if 0 != bytes.Compare(clientSeed, serverSeed) { - t.Fatalf("[%d:0] client/server seed mismatch", l) - } - } - - // Test oversized client padding. - clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) - if err != nil { - t.Fatalf("newClientHandshake failed: %s", err) - } - clientHs.padLen = clientMaxPadLength + 1 - clientBlob, err := clientHs.generateHandshake() - if err != nil { - t.Fatalf("clientHandshake.generateHandshake() (forced oversize) failed: %s", err) - } - serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) - _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) - if err == nil { - t.Fatalf("serverHandshake.parseClientHandshake() succeded (oversized)") - } - - // Test undersized client padding. - clientHs.padLen = clientMinPadLength - 1 - clientBlob, err = clientHs.generateHandshake() - if err != nil { - t.Fatalf("clientHandshake.generateHandshake() (forced undersize) failed: %s", err) - } - serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair) - _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) - if err == nil { - t.Fatalf("serverHandshake.parseClientHandshake() succeded (undersized)") - } -} - -func TestHandshakeNtorServer(t *testing.T) { - // Generate the server node id and id keypair, and ephemeral session keys. - nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13")) - idKeypair, _ := ntor.NewKeypair(false) - serverFilter, _ := replayfilter.New(replayTTL) - clientKeypair, err := ntor.NewKeypair(true) - if err != nil { - t.Fatalf("client: ntor.NewKeypair failed: %s", err) - } - serverKeypair, err := ntor.NewKeypair(true) - if err != nil { - t.Fatalf("server: ntor.NewKeypair failed: %s", err) - } - - // Test server handshake padding. - for l := serverMinPadLength; l <= serverMaxPadLength+inlineSeedFrameLength; l++ { - // Generate the client state and override the pad length. - clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) - clientHs.padLen = clientMinPadLength - - // Generate what the client will send to the server. - clientBlob, err := clientHs.generateHandshake() - if err != nil { - t.Fatalf("[%d:1] clientHandshake.generateHandshake() failed: %s", l, err) - } - if len(clientBlob) > maxHandshakeLength { - t.Fatalf("[%d:1] Generated client body is oversized: %d", l, len(clientBlob)) - } - - // Generate the server state and override the pad length. - serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) - serverHs.padLen = l - - // Parse the client handshake message. - serverSeed, err := serverHs.parseClientHandshake(serverFilter, clientBlob) - if err != nil { - t.Fatalf("[%d:1] serverHandshake.parseClientHandshake() failed: %s", l, err) - } - - // Genrate what the server will send to the client. - serverBlob, err := serverHs.generateHandshake() - if err != nil { - t.Fatalf("[%d:1]: serverHandshake.generateHandshake() failed: %s", l, err) - } - - // Parse the server handshake message. - n, clientSeed, err := clientHs.parseServerHandshake(serverBlob) - if err != nil { - t.Fatalf("[%d:1] clientHandshake.parseServerHandshake() failed: %s", l, err) - } - if n != len(serverBlob) { - t.Fatalf("[%d:1] clientHandshake.parseServerHandshake() has bytes remaining: %d", l, n) - } - - // Ensure the derived shared secret is the same. - if 0 != bytes.Compare(clientSeed, serverSeed) { - t.Fatalf("[%d:1] client/server seed mismatch", l) - } - } - - // Test oversized client padding. - clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair) - if err != nil { - t.Fatalf("newClientHandshake failed: %s", err) - } - clientHs.padLen = clientMaxPadLength + 1 - clientBlob, err := clientHs.generateHandshake() - if err != nil { - t.Fatalf("clientHandshake.generateHandshake() (forced oversize) failed: %s", err) - } - serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair) - _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) - if err == nil { - t.Fatalf("serverHandshake.parseClientHandshake() succeded (oversized)") - } - - // Test undersized client padding. - clientHs.padLen = clientMinPadLength - 1 - clientBlob, err = clientHs.generateHandshake() - if err != nil { - t.Fatalf("clientHandshake.generateHandshake() (forced undersize) failed: %s", err) - } - serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair) - _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) - if err == nil { - t.Fatalf("serverHandshake.parseClientHandshake() succeded (undersized)") - } - - // Test oversized server padding. - // - // NB: serverMaxPadLength isn't the real maxPadLength that triggers client - // rejection, because the implementation is written with the asusmption - // that the PRNG_SEED is also inlined with the response. Thus the client - // actually accepts longer padding. The server handshake test and this - // test adjust around that. - clientHs.padLen = clientMinPadLength - clientBlob, err = clientHs.generateHandshake() - if err != nil { - t.Fatalf("clientHandshake.generateHandshake() failed: %s", err) - } - serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair) - serverHs.padLen = serverMaxPadLength + inlineSeedFrameLength + 1 - _, err = serverHs.parseClientHandshake(serverFilter, clientBlob) - if err != nil { - t.Fatalf("serverHandshake.parseClientHandshake() failed: %s", err) - } - serverBlob, err := serverHs.generateHandshake() - if err != nil { - t.Fatalf("serverHandshake.generateHandshake() (forced oversize) failed: %s", err) - } - _, _, err = clientHs.parseServerHandshake(serverBlob) - if err == nil { - t.Fatalf("clientHandshake.parseServerHandshake() succeded (oversized)") - } -} diff --git a/transports/obfs4/obfs4.go b/transports/obfs4/obfs4.go deleted file mode 100644 index 269e1c6..0000000 --- a/transports/obfs4/obfs4.go +++ /dev/null @@ -1,646 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -// Package obfs4 provides an implementation of the Tor Project's obfs4 -// obfuscation protocol. -package obfs4 - -import ( - "bytes" - "crypto/sha256" - "flag" - "fmt" - "math/rand" - "net" - "strconv" - "syscall" - "time" - - "git.torproject.org/pluggable-transports/goptlib.git" - "github.com/OperatorFoundation/obfs4/common/drbg" - "github.com/OperatorFoundation/obfs4/common/ntor" - "github.com/OperatorFoundation/obfs4/common/probdist" - "github.com/OperatorFoundation/obfs4/common/replayfilter" - "github.com/OperatorFoundation/obfs4/transports/base" - "github.com/OperatorFoundation/obfs4/transports/obfs4/framing" -) - -const ( - transportName = "obfs4" - - nodeIDArg = "node-id" - publicKeyArg = "public-key" - privateKeyArg = "private-key" - seedArg = "drbg-seed" - iatArg = "iat-mode" - certArg = "cert" - - biasCmdArg = "obfs4-distBias" - - seedLength = drbg.SeedLength - headerLength = framing.FrameOverhead + packetOverhead - clientHandshakeTimeout = time.Duration(60) * time.Second - serverHandshakeTimeout = time.Duration(30) * time.Second - replayTTL = time.Duration(3) * time.Hour - - maxIATDelay = 100 - maxCloseDelayBytes = maxHandshakeLength - maxCloseDelay = 60 -) - -const ( - iatNone = iota - iatEnabled - iatParanoid -) - -// biasedDist controls if the probability table will be ScrambleSuit style or -// uniformly distributed. -var biasedDist bool - -type obfs4ClientArgs struct { - nodeID *ntor.NodeID - publicKey *ntor.PublicKey - sessionKey *ntor.Keypair - iatMode int -} - -// Transport is the obfs4 implementation of the base.Transport interface. -type Transport struct{} - -// Name returns the name of the obfs4 transport protocol. -func (t *Transport) Name() string { - return transportName -} - -// ClientFactory returns a new obfs4ClientFactory instance. -func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) { - cf := &obfs4ClientFactory{transport: t} - return cf, nil -} - -// ServerFactory returns a new obfs4ServerFactory instance. -func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) { - st, err := serverStateFromArgs(stateDir, args) - if err != nil { - return nil, err - } - - var iatSeed *drbg.Seed - if st.iatMode != iatNone { - iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:]) - var err error - iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]) - if err != nil { - return nil, err - } - } - - // Store the arguments that should appear in our descriptor for the clients. - ptArgs := pt.Args{} - ptArgs.Add(certArg, st.cert.String()) - ptArgs.Add(iatArg, strconv.Itoa(st.iatMode)) - - // Initialize the replay filter. - filter, err := replayfilter.New(replayTTL) - if err != nil { - return nil, err - } - - // Initialize the close thresholds for failed connections. - drbg, err := drbg.NewHashDrbg(st.drbgSeed) - if err != nil { - return nil, err - } - rng := rand.New(drbg) - - sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)} - return sf, nil -} - -type obfs4ClientFactory struct { - transport base.Transport -} - -func (cf *obfs4ClientFactory) Transport() base.Transport { - return cf.transport -} - -func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) { - var nodeID *ntor.NodeID - var publicKey *ntor.PublicKey - - // The "new" (version >= 0.0.3) bridge lines use a unified "cert" argument - // for the Node ID and Public Key. - certStr, ok := args.Get(certArg) - if ok { - cert, err := serverCertFromString(certStr) - if err != nil { - return nil, err - } - nodeID, publicKey = cert.unpack() - } else { - // The "old" style (version <= 0.0.2) bridge lines use separate Node ID - // and Public Key arguments in Base16 encoding and are a UX disaster. - nodeIDStr, ok := args.Get(nodeIDArg) - if !ok { - return nil, fmt.Errorf("missing argument '%s'", nodeIDArg) - } - var err error - if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil { - return nil, err - } - - publicKeyStr, ok := args.Get(publicKeyArg) - if !ok { - return nil, fmt.Errorf("missing argument '%s'", publicKeyArg) - } - if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil { - return nil, err - } - } - - // IAT config is common across the two bridge line formats. - iatStr, ok := args.Get(iatArg) - if !ok { - return nil, fmt.Errorf("missing argument '%s'", iatArg) - } - iatMode, err := strconv.Atoi(iatStr) - if err != nil || iatMode < iatNone || iatMode > iatParanoid { - return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode) - } - - // Generate the session key pair before connectiong to hide the Elligator2 - // rejection sampling from network observers. - sessionKey, err := ntor.NewKeypair(true) - if err != nil { - return nil, err - } - - return &obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}, nil -} - -func (cf *obfs4ClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) { - // Validate args before bothering to open connection. - ca, ok := args.(*obfs4ClientArgs) - if !ok { - return nil, fmt.Errorf("invalid argument type for args") - } - conn, err := dialFn(network, addr) - if err != nil { - return nil, err - } - dialConn := conn - if conn, err = newObfs4ClientConn(conn, ca); err != nil { - dialConn.Close() - return nil, err - } - return conn, nil -} - -type obfs4ServerFactory struct { - transport base.Transport - args *pt.Args - - nodeID *ntor.NodeID - identityKey *ntor.Keypair - lenSeed *drbg.Seed - iatSeed *drbg.Seed - iatMode int - replayFilter *replayfilter.ReplayFilter - - closeDelayBytes int - closeDelay int -} - -func (sf *obfs4ServerFactory) Transport() base.Transport { - return sf.transport -} - -func (sf *obfs4ServerFactory) Args() *pt.Args { - return sf.args -} - -func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) { - // Not much point in having a separate newObfs4ServerConn routine when - // wrapping requires using values from the factory instance. - - // Generate the session keypair *before* consuming data from the peer, to - // attempt to mask the rejection sampling due to use of Elligator2. This - // might be futile, but the timing differential isn't very large on modern - // hardware, and there are far easier statistical attacks that can be - // mounted as a distinguisher. - sessionKey, err := ntor.NewKeypair(true) - if err != nil { - return nil, err - } - - lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist) - var iatDist *probdist.WeightedDist - if sf.iatSeed != nil { - iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist) - } - - c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil} - - startTime := time.Now() - - if err = c.serverHandshake(sf, sessionKey); err != nil { - c.closeAfterDelay(sf, startTime) - return nil, err - } - - return c, nil -} - -type obfs4Conn struct { - net.Conn - - isServer bool - - lenDist *probdist.WeightedDist - iatDist *probdist.WeightedDist - iatMode int - - receiveBuffer *bytes.Buffer - receiveDecodedBuffer *bytes.Buffer - - encoder *framing.Encoder - decoder *framing.Decoder -} - -func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) { - // Generate the initial protocol polymorphism distribution(s). - var seed *drbg.Seed - if seed, err = drbg.NewSeed(); err != nil { - return - } - lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist) - var iatDist *probdist.WeightedDist - if args.iatMode != iatNone { - var iatSeed *drbg.Seed - iatSeedSrc := sha256.Sum256(seed.Bytes()[:]) - if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil { - return - } - iatDist = probdist.New(iatSeed, 0, maxIATDelay, biasedDist) - } - - // Allocate the client structure. - c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil} - - // Start the handshake timeout. - deadline := time.Now().Add(clientHandshakeTimeout) - if err = conn.SetDeadline(deadline); err != nil { - return nil, err - } - - if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil { - return nil, err - } - - // Stop the handshake timeout. - if err = conn.SetDeadline(time.Time{}); err != nil { - return nil, err - } - - return -} - -func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error { - if conn.isServer { - return fmt.Errorf("clientHandshake called on server connection") - } - - // Generate and send the client handshake. - hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey) - blob, err := hs.generateHandshake() - if err != nil { - return err - } - if _, err = conn.Conn.Write(blob); err != nil { - return err - } - - // Consume the server handshake. - var hsBuf [maxHandshakeLength]byte - for { - n, err := conn.Conn.Read(hsBuf[:]) - if err != nil { - // The Read() could have returned data and an error, but there is - // no point in continuing on an EOF or whatever. - return err - } - conn.receiveBuffer.Write(hsBuf[:n]) - - n, seed, err := hs.parseServerHandshake(conn.receiveBuffer.Bytes()) - if err == ErrMarkNotFoundYet { - continue - } else if err != nil { - return err - } - _ = conn.receiveBuffer.Next(n) - - // Use the derived key material to intialize the link crypto. - okm := ntor.Kdf(seed, framing.KeyLength*2) - conn.encoder = framing.NewEncoder(okm[:framing.KeyLength]) - conn.decoder = framing.NewDecoder(okm[framing.KeyLength:]) - - return nil - } -} - -func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.Keypair) error { - if !conn.isServer { - return fmt.Errorf("serverHandshake called on client connection") - } - - // Generate the server handshake, and arm the base timeout. - hs := newServerHandshake(sf.nodeID, sf.identityKey, sessionKey) - if err := conn.Conn.SetDeadline(time.Now().Add(serverHandshakeTimeout)); err != nil { - return err - } - - // Consume the client handshake. - var hsBuf [maxHandshakeLength]byte - for { - n, err := conn.Conn.Read(hsBuf[:]) - if err != nil { - // The Read() could have returned data and an error, but there is - // no point in continuing on an EOF or whatever. - return err - } - conn.receiveBuffer.Write(hsBuf[:n]) - - seed, err := hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes()) - if err == ErrMarkNotFoundYet { - continue - } else if err != nil { - return err - } - conn.receiveBuffer.Reset() - - if err := conn.Conn.SetDeadline(time.Time{}); err != nil { - return nil - } - - // Use the derived key material to intialize the link crypto. - okm := ntor.Kdf(seed, framing.KeyLength*2) - conn.encoder = framing.NewEncoder(okm[framing.KeyLength:]) - conn.decoder = framing.NewDecoder(okm[:framing.KeyLength]) - - break - } - - // Since the current and only implementation always sends a PRNG seed for - // the length obfuscation, this makes the amount of data received from the - // server inconsistent with the length sent from the client. - // - // Rebalance this by tweaking the client mimimum padding/server maximum - // padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed - // as part of the server response). See inlineSeedFrameLength in - // handshake_ntor.go. - - // Generate/send the response. - blob, err := hs.generateHandshake() - if err != nil { - return err - } - var frameBuf bytes.Buffer - if _, err = frameBuf.Write(blob); err != nil { - return err - } - - // Send the PRNG seed as the first packet. - if err := conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil { - return err - } - if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil { - return err - } - - return nil -} - -func (conn *obfs4Conn) Read(b []byte) (n int, err error) { - // If there is no payload from the previous Read() calls, consume data off - // the network. Not all data received is guaranteed to be usable payload, - // so do this in a loop till data is present or an error occurs. - for conn.receiveDecodedBuffer.Len() == 0 { - err = conn.readPackets() - if err == framing.ErrAgain { - // Don't proagate this back up the call stack if we happen to break - // out of the loop. - err = nil - continue - } else if err != nil { - break - } - } - - // Even if err is set, attempt to do the read anyway so that all decoded - // data gets relayed before the connection is torn down. - if conn.receiveDecodedBuffer.Len() > 0 { - var berr error - n, berr = conn.receiveDecodedBuffer.Read(b) - if err == nil { - // Only propagate berr if there are not more important (fatal) - // errors from the network/crypto/packet processing. - err = berr - } - } - - return -} - -func (conn *obfs4Conn) Write(b []byte) (n int, err error) { - chopBuf := bytes.NewBuffer(b) - var payload [maxPacketPayloadLength]byte - var frameBuf bytes.Buffer - - // Chop the pending data into payload frames. - for chopBuf.Len() > 0 { - // Send maximum sized frames. - rdLen := 0 - rdLen, err = chopBuf.Read(payload[:]) - if err != nil { - return 0, err - } else if rdLen == 0 { - panic(fmt.Sprintf("BUG: Write(), chopping length was 0")) - } - n += rdLen - - err = conn.makePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0) - if err != nil { - return 0, err - } - } - - if conn.iatMode != iatParanoid { - // For non-paranoid IAT, pad once per burst. Paranoid IAT handles - // things differently. - if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil { - return 0, err - } - } - - // Write the pending data onto the network. Partial writes are fatal, - // because the frame encoder state is advanced, and the code doesn't keep - // frameBuf around. In theory, write timeouts and whatnot could be - // supported if this wasn't the case, but that complicates the code. - if conn.iatMode != iatNone { - var iatFrame [framing.MaximumSegmentLength]byte - for frameBuf.Len() > 0 { - iatWrLen := 0 - - switch conn.iatMode { - case iatEnabled: - // Standard (ScrambleSuit-style) IAT obfuscation optimizes for - // bulk transport and will write ~MTU sized frames when - // possible. - iatWrLen, err = frameBuf.Read(iatFrame[:]) - - case iatParanoid: - // Paranoid IAT obfuscation throws performance out of the - // window and will sample the length distribution every time a - // write is scheduled. - targetLen := conn.lenDist.Sample() - if frameBuf.Len() < targetLen { - // There's not enough data buffered for the target write, - // so padding must be inserted. - if err = conn.padBurst(&frameBuf, targetLen); err != nil { - return 0, err - } - if frameBuf.Len() != targetLen { - // Ugh, padding came out to a value that required more - // than one frame, this is relatively unlikely so just - // resample since there's enough data to ensure that - // the next sample will be written. - continue - } - } - iatWrLen, err = frameBuf.Read(iatFrame[:targetLen]) - } - if err != nil { - return 0, err - } else if iatWrLen == 0 { - panic(fmt.Sprintf("BUG: Write(), iat length was 0")) - } - - // Calculate the delay. The delay resolution is 100 usec, leading - // to a maximum delay of 10 msec. - iatDelta := time.Duration(conn.iatDist.Sample() * 100) - - // Write then sleep. - _, err = conn.Conn.Write(iatFrame[:iatWrLen]) - if err != nil { - return 0, err - } - time.Sleep(iatDelta * time.Microsecond) - } - } else { - _, err = conn.Conn.Write(frameBuf.Bytes()) - } - - return -} - -func (conn *obfs4Conn) SetDeadline(t time.Time) error { - return syscall.ENOTSUP -} - -func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error { - return syscall.ENOTSUP -} - -func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) { - // I-it's not like I w-wanna handshake with you or anything. B-b-baka! - defer conn.Conn.Close() - - delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout - deadline := startTime.Add(delay) - if time.Now().After(deadline) { - return - } - - if err := conn.Conn.SetReadDeadline(deadline); err != nil { - return - } - - // Consume and discard data on this connection until either the specified - // interval passes or a certain size has been reached. - discarded := 0 - var buf [framing.MaximumSegmentLength]byte - for discarded < int(sf.closeDelayBytes) { - n, err := conn.Conn.Read(buf[:]) - if err != nil { - return - } - discarded += n - } -} - -func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) { - tailLen := burst.Len() % framing.MaximumSegmentLength - - padLen := 0 - if toPadTo >= tailLen { - padLen = toPadTo - tailLen - } else { - padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo - } - - if padLen > headerLength { - err = conn.makePacket(burst, packetTypePayload, []byte{}, - uint16(padLen-headerLength)) - if err != nil { - return - } - } else if padLen > 0 { - err = conn.makePacket(burst, packetTypePayload, []byte{}, - maxPacketPayloadLength) - if err != nil { - return - } - err = conn.makePacket(burst, packetTypePayload, []byte{}, - uint16(padLen)) - if err != nil { - return - } - } - - return -} - -func init() { - flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation") -} - -var _ base.ClientFactory = (*obfs4ClientFactory)(nil) -var _ base.ServerFactory = (*obfs4ServerFactory)(nil) -var _ base.Transport = (*Transport)(nil) -var _ net.Conn = (*obfs4Conn)(nil) diff --git a/transports/obfs4/packet.go b/transports/obfs4/packet.go deleted file mode 100644 index ac3028a..0000000 --- a/transports/obfs4/packet.go +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package obfs4 - -import ( - "crypto/sha256" - "encoding/binary" - "fmt" - "io" - - "github.com/OperatorFoundation/obfs4/common/drbg" - "github.com/OperatorFoundation/obfs4/transports/obfs4/framing" -) - -const ( - packetOverhead = 2 + 1 - maxPacketPayloadLength = framing.MaximumFramePayloadLength - packetOverhead - maxPacketPaddingLength = maxPacketPayloadLength - seedPacketPayloadLength = seedLength - - consumeReadSize = framing.MaximumSegmentLength * 16 -) - -const ( - packetTypePayload = iota - packetTypePrngSeed -) - -// InvalidPacketLengthError is the error returned when decodePacket detects a -// invalid packet length/ -type InvalidPacketLengthError int - -func (e InvalidPacketLengthError) Error() string { - return fmt.Sprintf("packet: Invalid packet length: %d", int(e)) -} - -// InvalidPayloadLengthError is the error returned when decodePacket rejects the -// payload length. -type InvalidPayloadLengthError int - -func (e InvalidPayloadLengthError) Error() string { - return fmt.Sprintf("packet: Invalid payload length: %d", int(e)) -} - -var zeroPadBytes [maxPacketPaddingLength]byte - -func (conn *obfs4Conn) makePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) error { - var pkt [framing.MaximumFramePayloadLength]byte - - if len(data)+int(padLen) > maxPacketPayloadLength { - panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPacketPayloadLength: %d + %d > %d", - len(data), padLen, maxPacketPayloadLength)) - } - - // Packets are: - // uint8_t type packetTypePayload (0x00) - // uint16_t length Length of the payload (Big Endian). - // uint8_t[] payload Data payload. - // uint8_t[] padding Padding. - pkt[0] = pktType - binary.BigEndian.PutUint16(pkt[1:], uint16(len(data))) - if len(data) > 0 { - copy(pkt[3:], data[:]) - } - copy(pkt[3+len(data):], zeroPadBytes[:padLen]) - - pktLen := packetOverhead + len(data) + int(padLen) - - // Encode the packet in an AEAD frame. - var frame [framing.MaximumSegmentLength]byte - frameLen, err := conn.encoder.Encode(frame[:], pkt[:pktLen]) - if err != nil { - // All encoder errors are fatal. - return err - } - wrLen, err := w.Write(frame[:frameLen]) - if err != nil { - return err - } else if wrLen < frameLen { - return io.ErrShortWrite - } - - return nil -} - -func (conn *obfs4Conn) readPackets() (err error) { - // Attempt to read off the network. - var buf [consumeReadSize]byte - rdLen, rdErr := conn.Conn.Read(buf[:]) - conn.receiveBuffer.Write(buf[:rdLen]) - - var decoded [framing.MaximumFramePayloadLength]byte - for conn.receiveBuffer.Len() > 0 { - // Decrypt an AEAD frame. - decLen := 0 - decLen, err = conn.decoder.Decode(decoded[:], conn.receiveBuffer) - if err == framing.ErrAgain { - break - } else if err != nil { - break - } else if decLen < packetOverhead { - err = InvalidPacketLengthError(decLen) - break - } - - // Decode the packet. - pkt := decoded[0:decLen] - pktType := pkt[0] - payloadLen := binary.BigEndian.Uint16(pkt[1:]) - if int(payloadLen) > len(pkt)-packetOverhead { - err = InvalidPayloadLengthError(int(payloadLen)) - break - } - payload := pkt[3 : 3+payloadLen] - - switch pktType { - case packetTypePayload: - if payloadLen > 0 { - conn.receiveDecodedBuffer.Write(payload) - } - case packetTypePrngSeed: - // Only regenerate the distribution if we are the client. - if len(payload) == seedPacketPayloadLength && !conn.isServer { - var seed *drbg.Seed - seed, err = drbg.SeedFromBytes(payload) - if err != nil { - break - } - conn.lenDist.Reset(seed) - if conn.iatDist != nil { - iatSeedSrc := sha256.Sum256(seed.Bytes()[:]) - iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:]) - if err != nil { - break - } - conn.iatDist.Reset(iatSeed) - } - } - default: - // Ignore unknown packet types. - } - } - - // Read errors (all fatal) take priority over various frame processing - // errors. - if rdErr != nil { - return rdErr - } - - return -} diff --git a/transports/obfs4/statefile.go b/transports/obfs4/statefile.go deleted file mode 100644 index 6f89c6c..0000000 --- a/transports/obfs4/statefile.go +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org> - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * * Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package obfs4 - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path" - "strconv" - "strings" - - "git.torproject.org/pluggable-transports/goptlib.git" - "github.com/OperatorFoundation/obfs4/common/csrand" - "github.com/OperatorFoundation/obfs4/common/drbg" - "github.com/OperatorFoundation/obfs4/common/ntor" -) - -const ( - stateFile = "obfs4_state.json" - bridgeFile = "obfs4_bridgeline.txt" - - certSuffix = "==" - certLength = ntor.NodeIDLength + ntor.PublicKeyLength -) - -type jsonServerState struct { - NodeID string `json:"node-id"` - PrivateKey string `json:"private-key"` - PublicKey string `json:"public-key"` - DrbgSeed string `json:"drbg-seed"` - IATMode int `json:"iat-mode"` -} - -type obfs4ServerCert struct { - raw []byte -} - -func (cert *obfs4ServerCert) String() string { - return strings.TrimSuffix(base64.StdEncoding.EncodeToString(cert.raw), certSuffix) -} - -func (cert *obfs4ServerCert) unpack() (*ntor.NodeID, *ntor.PublicKey) { - if len(cert.raw) != certLength { - panic(fmt.Sprintf("cert length %d is invalid", len(cert.raw))) - } - - nodeID, _ := ntor.NewNodeID(cert.raw[:ntor.NodeIDLength]) - pubKey, _ := ntor.NewPublicKey(cert.raw[ntor.NodeIDLength:]) - - return nodeID, pubKey -} - -func serverCertFromString(encoded string) (*obfs4ServerCert, error) { - decoded, err := base64.StdEncoding.DecodeString(encoded + certSuffix) - if err != nil { - return nil, fmt.Errorf("failed to decode cert: %s", err) - } - - if len(decoded) != certLength { - return nil, fmt.Errorf("cert length %d is invalid", len(decoded)) - } - - return &obfs4ServerCert{raw: decoded}, nil -} - -func serverCertFromState(st *obfs4ServerState) *obfs4ServerCert { - cert := new(obfs4ServerCert) - cert.raw = append(st.nodeID.Bytes()[:], st.identityKey.Public().Bytes()[:]...) - return cert -} - -type obfs4ServerState struct { - nodeID *ntor.NodeID - identityKey *ntor.Keypair - drbgSeed *drbg.Seed - iatMode int - - cert *obfs4ServerCert -} - -func (st *obfs4ServerState) clientString() string { - return fmt.Sprintf("%s=%s %s=%d", certArg, st.cert, iatArg, st.iatMode) -} - -func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) { - var js jsonServerState - var nodeIDOk, privKeyOk, seedOk bool - - js.NodeID, nodeIDOk = args.Get(nodeIDArg) - js.PrivateKey, privKeyOk = args.Get(privateKeyArg) - js.DrbgSeed, seedOk = args.Get(seedArg) - iatStr, iatOk := args.Get(iatArg) - - if !privKeyOk && !nodeIDOk && !seedOk && !iatOk { - if err := jsonServerStateFromFile(stateDir, &js); err != nil { - return nil, err - } - } else if !privKeyOk { - return nil, fmt.Errorf("missing argument '%s'", privateKeyArg) - } else if !nodeIDOk { - return nil, fmt.Errorf("missing argument '%s'", nodeIDArg) - } else if !seedOk { - return nil, fmt.Errorf("missing argument '%s'", seedArg) - } else if !iatOk { - // Disable IAT if not specified. - return nil, fmt.Errorf("missing argument '%s'", iatArg) - } else { - // Parse and validate the iat-mode argument. - iatMode, err := strconv.Atoi(iatStr) - if err != nil { - return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr) - } - js.IATMode = iatMode - } - - return serverStateFromJSONServerState(stateDir, &js) -} - -func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs4ServerState, error) { - var err error - - st := new(obfs4ServerState) - if st.nodeID, err = ntor.NodeIDFromHex(js.NodeID); err != nil { - return nil, err - } - if st.identityKey, err = ntor.KeypairFromHex(js.PrivateKey); err != nil { - return nil, err - } - if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil { - return nil, err - } - if js.IATMode < iatNone || js.IATMode > iatParanoid { - return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode) - } - st.iatMode = js.IATMode - st.cert = serverCertFromState(st) - - // Generate a human readable summary of the configured endpoint. - if err = newBridgeFile(stateDir, st); err != nil { - return nil, err - } - - return st, nil -} - -func jsonServerStateFromFile(stateDir string, js *jsonServerState) error { - fPath := path.Join(stateDir, stateFile) - f, err := ioutil.ReadFile(fPath) - if err != nil { - if os.IsNotExist(err) { - if err = newJSONServerState(stateDir, js); err == nil { - return nil - } - } - return err - } - - if err := json.Unmarshal(f, js); err != nil { - return fmt.Errorf("failed to load statefile '%s': %s", fPath, err) - } - - return nil -} - -func newJSONServerState(stateDir string, js *jsonServerState) (err error) { - // Generate everything a server needs, using the cryptographic PRNG. - var st obfs4ServerState - rawID := make([]byte, ntor.NodeIDLength) - if err = csrand.Bytes(rawID); err != nil { - return - } - if st.nodeID, err = ntor.NewNodeID(rawID); err != nil { - return - } - if st.identityKey, err = ntor.NewKeypair(false); err != nil { - return - } - if st.drbgSeed, err = drbg.NewSeed(); err != nil { - return - } - st.iatMode = iatNone - - // Encode it into JSON format and write the state file. - js.NodeID = st.nodeID.Hex() - js.PrivateKey = st.identityKey.Private().Hex() - js.PublicKey = st.identityKey.Public().Hex() - js.DrbgSeed = st.drbgSeed.Hex() - js.IATMode = st.iatMode - - var encoded []byte - if encoded, err = json.Marshal(js); err != nil { - return - } - - if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil { - return err - } - - return nil -} - -func newBridgeFile(stateDir string, st *obfs4ServerState) error { - const prefix = "# obfs4 torrc client bridge line\n" + - "#\n" + - "# This file is an automatically generated bridge line based on\n" + - "# the current obfs4proxy configuration. EDITING IT WILL HAVE\n" + - "# NO EFFECT.\n" + - "#\n" + - "# Before distributing this Bridge, edit the placeholder fields\n" + - "# to contain the actual values:\n" + - "# <IP ADDRESS> - The public IP address of your obfs4 bridge.\n" + - "# <PORT> - The TCP/IP port of your obfs4 bridge.\n" + - "# <FINGERPRINT> - The bridge's fingerprint.\n\n" - - bridgeLine := fmt.Sprintf("Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> %s\n", - st.clientString()) - - tmp := []byte(prefix + bridgeLine) - if err := ioutil.WriteFile(path.Join(stateDir, bridgeFile), tmp, 0600); err != nil { - return err - } - - return nil -} |