summaryrefslogtreecommitdiff
path: root/transports/obfs4
diff options
context:
space:
mode:
Diffstat (limited to 'transports/obfs4')
-rw-r--r--transports/obfs4/framing/framing.go306
-rw-r--r--transports/obfs4/framing/framing_test.go169
-rw-r--r--transports/obfs4/handshake_ntor.go425
-rw-r--r--transports/obfs4/handshake_ntor_test.go248
-rw-r--r--transports/obfs4/obfs4.go646
-rw-r--r--transports/obfs4/packet.go176
-rw-r--r--transports/obfs4/statefile.go252
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
-}