[Pkg-privacy-commits] [obfs4proxy] 01/151: Initial import.

Ximin Luo infinity0 at moszumanska.debian.org
Sat Aug 22 12:59:33 UTC 2015


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository obfs4proxy.

commit ef38b844f9989dff25f553b583aeafc411dd100e
Author: Yawning Angel <yawning at schwanenlied.me>
Date:   Fri May 9 10:23:58 2014 +0000

    Initial import.
---
 .gitignore                   |   5 +
 README.md                    |  54 ++++++
 framing/framing.go           | 303 ++++++++++++++++++++++++++++++
 framing/framing_test.go      | 178 ++++++++++++++++++
 handshake_ntor.go            | 386 ++++++++++++++++++++++++++++++++++++++
 handshake_ntor_test.go       |  80 ++++++++
 ntor/ntor.go                 | 435 +++++++++++++++++++++++++++++++++++++++++++
 ntor/ntor_test.go            | 182 ++++++++++++++++++
 obfs4-client/obfs4-client.go | 183 ++++++++++++++++++
 obfs4-server/obfs4-server.go | 225 ++++++++++++++++++++++
 obfs4.go                     | 399 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 2430 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5eac799
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+*.swp
+*~
+
+obfs4-client/obfs4-client
+obfs4-server/obfs4-server
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bf1d69c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,54 @@
+## obfs4 - The fourbfuscator
+#### Yawning Angel (yawning at torproject dot org)
+
+### WARNING
+
+This is pre-alpha.  Don't expect any security or wire protocol stability yet.
+If you want to use something like this, you should currently probably be looking
+at ScrambleSuit.
+
+### What?
+
+This is a look-like nothing obfuscation protocol that incorporates ideas and
+concepts from Philipp Winter's ScrambleSuit protocol.  The obfs naming was
+chosen primarily because it was shorter, in terms of protocol ancestery obfs4
+is much closer to ScrambleSuit than obfs2/obfs3.
+
+The notable differences between ScrambleSuit and obfs4:
+
+ * The handshake always does a full key exchange (no such thing as a Session
+   Ticket Handshake). (TODO: Reconsider this.)
+ * The handshake uses the Tor Project's ntor handshake with public keys
+   obfuscated via the Elligator mapping.
+ * The link layer encryption uses NaCl secret boxes (Poly1305/Salsa20).
+
+### Why not extend ScrambleSuit?
+
+It's my protocol and I'll obfuscate if I want to.
+
+Since a lot of the changes are to the handshaking process, it didn't make sense
+to extend ScrambleSuit as writing a server implementation that supported both
+handshake variants without being obscenely slow is non-trivial.
+
+### TODO
+
+ * Packet length obfuscation.
+ * (Maybe) Make it resilient to transient connection loss.
+ * (Maybe) Use IP_MTU/TCP_MAXSEG to tweak frame size.
+ * Write a detailed protocol spec.
+ * Code cleanups.
+ * Write more unit tests.
+
+### WON'T DO
+
+ * I do not care that much about standalone mode.  Patches *MAY* be accepted,
+   especially if they are clean and are useful to Tor users.
+ * Yes, I use a bunch of code from the borg^w^wGoogle.  If that bothers you
+   feel free to write your own implementation.
+ * I do not care about older versions of the go runtime.
+
+### Thanks
+ * David Fifield for goptlib.
+ * Adam Langley for his Elligator implementation.
+ * Philipp Winter for the ScrambleSuit protocol which provided much of the
+   design.
diff --git a/framing/framing.go b/framing/framing.go
new file mode 100644
index 0000000..d319207
--- /dev/null
+++ b/framing/framing.go
@@ -0,0 +1,303 @@
+/*
+ * 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[24] NaCl Nonce prefix
+//    uint8_t[16] SipHash-2-4 key (used to obfsucate length)
+//
+// The frame format is:
+//   uint16_t length (obfsucated, big endian)
+//   NaCl SecretBox (Poly1305/Salsa20) 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 of the previous SecretBox concatenated with the nonce
+// used to seal the current SecretBox.
+//
+// The NaCl SecretBox (Poly1305/Salsa20) 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"
+	"hash"
+
+	"code.google.com/p/go.crypto/nacl/secretbox"
+
+	"github.com/dchest/siphash"
+)
+
+const (
+	// MaximumSegmentLength is the length of the largest possible segment
+	// including overhead.
+	MaximumSegmentLength = 1500 - 40
+
+	// 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 + 16
+
+	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))
+}
+
+// InvalidFrameLengthError is the error returned when Decoder.Decode()
+// rejects the payload length.
+type InvalidFrameLengthError int
+
+func (e InvalidFrameLengthError) Error() string {
+	return fmt.Sprintf("framing: Invalid frame 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
+	sip   hash.Hash64
+	nonce boxNonce
+}
+
+// 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])
+	encoder.sip = siphash.New(key[keyLength+noncePrefixLength:])
+
+	return encoder
+}
+
+// Encode encodes a single frame worth of payload and returns the encoded
+// length and the resulting frame.  InvalidPayloadLengthError is recoverable,
+// all other errors MUST be treated as fatal and the session aborted.
+func (encoder *Encoder) Encode(payload []byte) (int, []byte, error) {
+	payloadLen := len(payload)
+	if MaximumFramePayloadLength < payloadLen {
+		return 0, nil, InvalidPayloadLengthError(payloadLen)
+	}
+
+	// Generate a new nonce.
+	var nonce [nonceLength]byte
+	err := encoder.nonce.bytes(&nonce)
+	if err != nil {
+		return 0, nil, err
+	}
+	encoder.nonce.counter++
+
+	// Encrypt and MAC payload.
+	var box []byte
+	box = secretbox.Seal(nil, payload, &nonce, &encoder.key)
+
+	// Obfuscate the length.
+	length := uint16(len(box))
+	encoder.sip.Write(nonce[:])
+	lengthMask := encoder.sip.Sum(nil)
+	encoder.sip.Reset()
+	length ^= binary.BigEndian.Uint16(lengthMask)
+	var obfsLen [lengthLength]byte
+	binary.BigEndian.PutUint16(obfsLen[:], length)
+
+	// Prepare the next obfsucator.
+	encoder.sip.Write(box)
+
+	// Return the frame.
+	return payloadLen + FrameOverhead, append(obfsLen[:], box...), nil
+}
+
+// Decoder is a frame decoder instance.
+type Decoder struct {
+	key   [keyLength]byte
+	nonce boxNonce
+	sip   hash.Hash64
+
+	nextNonce  [nonceLength]byte
+	nextLength uint16
+}
+
+// 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])
+	decoder.sip = siphash.New(key[keyLength+noncePrefixLength:])
+
+	return decoder
+}
+
+// Decode decodes a stream of data and returns the length and decoded frame if
+// any.  ErrAgain is a temporary failure, all other errors MUST be treated as
+// fatal and the session aborted.
+func (decoder *Decoder) Decode(data *bytes.Buffer) (int, []byte, 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 > data.Len() {
+			return 0, nil, ErrAgain
+		}
+
+		// Remove the length field from the buffer.
+		var obfsLen [lengthLength]byte
+		n, err := data.Read(obfsLen[:])
+		if err != nil {
+			return 0, nil, err
+		} else if n != lengthLength {
+			// Should *NEVER* happen, since at least 2 bytes exist.
+			panic(fmt.Sprintf("BUG: Failed to read obfuscated length: %d", n))
+		}
+
+		// Derive the nonce the peer used.
+		err = decoder.nonce.bytes(&decoder.nextNonce)
+		if err != nil {
+			return 0, nil, err
+		}
+
+		// Deobfuscate the length field.
+		length := binary.BigEndian.Uint16(obfsLen[:])
+		decoder.sip.Write(decoder.nextNonce[:])
+		lengthMask := decoder.sip.Sum(nil)
+		decoder.sip.Reset()
+		length ^= binary.BigEndian.Uint16(lengthMask)
+		if maxFrameLength < length || minFrameLength > length {
+			return 0, nil, InvalidFrameLengthError(length)
+		}
+		decoder.nextLength = length
+	}
+
+	if int(decoder.nextLength) > data.Len() {
+		return 0, nil, ErrAgain
+	}
+
+	// Unseal the frame.
+	box := make([]byte, decoder.nextLength)
+	n, err := data.Read(box)
+	if err != nil {
+		return 0, nil, err
+	} else if n != int(decoder.nextLength) {
+		// Should *NEVER* happen, since at least 2 bytes exist.
+		panic(fmt.Sprintf("BUG: Failed to read secretbox, got %d, should have %d", n,
+			decoder.nextLength))
+	}
+	out, ok := secretbox.Open(nil, box, &decoder.nextNonce, &decoder.key)
+	if !ok {
+		return 0, nil, ErrTagMismatch
+	}
+	decoder.sip.Write(box)
+
+	// Clean up and prepare for the next frame.
+	decoder.nextLength = 0
+	decoder.nonce.counter++
+
+	return len(out), out, nil
+}
+
+/* vim :set ts=4 sw=4 sts=4 noet : */
diff --git a/framing/framing_test.go b/framing/framing_test.go
new file mode 100644
index 0000000..221ea5e
--- /dev/null
+++ b/framing/framing_test.go
@@ -0,0 +1,178 @@
+/*
+ * 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++ {
+		n, frame, err := encoder.Encode(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)
+		}
+		if len(frame) != n {
+			t.Fatalf("Encoded frame length/rval mismatch: %d != %d",
+				len(frame), n)
+		}
+	}
+}
+
+// TestEncoder_Encode_Oversize tests oversized frame rejection.
+func TestEncoder_Encode_Oversize(t *testing.T) {
+	encoder := newEncoder(t)
+
+	buf := make([]byte, MaximumFramePayloadLength+1)
+	_, _ = rand.Read(buf) // YOLO
+	_, _, err := encoder.Encode(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)
+
+	buf := make([]byte, MaximumFramePayloadLength)
+	_, _ = rand.Read(buf) // YOLO
+	for i := 0; i <= MaximumFramePayloadLength; i++ {
+		encLen, frame, err := encoder.Encode(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)
+		}
+		if len(frame) != encLen {
+			t.Fatalf("Encoded frame length/rval mismatch: %d != %d",
+				len(frame), encLen)
+		}
+
+		decLen, decoded, err := decoder.Decode(bytes.NewBuffer(frame))
+		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 len(decoded) != i {
+			t.Fatalf("Encoded frame length/rval mismatch: %d != %d",
+				len(decoded), i)
+
+		}
+
+		if 0 != bytes.Compare(decoded, buf[0: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
+	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, frame, err := encoder.Encode(chopBuf[:n])
+			transfered += len(frame) - FrameOverhead
+		}
+		if transfered != len(payload) {
+			b.Fatalf("Transfered length mismatch: %d != %d", transfered,
+				len(payload))
+		}
+	}
+}
+
+/* vim :set ts=4 sw=4 sts=4 noet : */
diff --git a/handshake_ntor.go b/handshake_ntor.go
new file mode 100644
index 0000000..44680aa
--- /dev/null
+++ b/handshake_ntor.go
@@ -0,0 +1,386 @@
+/*
+ * 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/rand"
+	"crypto/sha256"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"hash"
+	"math/big"
+	"strconv"
+	"time"
+
+	"github.com/yawning/obfs4/framing"
+	"github.com/yawning/obfs4/ntor"
+)
+
+const (
+	clientMinPadLength       = serverMinHandshakeLength - clientMinHandshakeLength
+	clientMaxPadLength       = framing.MaximumSegmentLength - clientMinHandshakeLength
+	clientMinHandshakeLength = ntor.RepresentativeLength + markLength + macLength
+	clientMaxHandshakeLength = framing.MaximumSegmentLength
+
+	serverMinPadLength       = 0
+	serverMaxPadLength       = framing.MaximumSegmentLength - serverMinHandshakeLength
+	serverMinHandshakeLength = ntor.RepresentativeLength + ntor.AuthLength +
+		markLength + macLength
+	serverMaxHandshakeLength = framing.MaximumSegmentLength
+
+	markLength = sha256.Size
+	macLength  = sha256.Size
+)
+
+var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet")
+var ErrInvalidHandshake = errors.New("handshake: Failed to find M_[C,S]")
+var ErrNtorFailed = errors.New("handshake: ntor handshake failure")
+
+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))
+}
+
+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
+
+	mac hash.Hash
+
+	serverRepresentative *ntor.Representative
+	serverAuth           *ntor.Auth
+	serverMark           []byte
+}
+
+func newClientHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.PublicKey) (*clientHandshake, error) {
+	var err error
+
+	hs := new(clientHandshake)
+	hs.keypair, err = ntor.NewKeypair(true)
+	if err != nil {
+		return nil, err
+	}
+	hs.nodeID = nodeID
+	hs.serverIdentity = serverIdentity
+	hs.mac = hmac.New(sha256.New, hs.serverIdentity.Bytes()[:])
+
+	return hs, nil
+}
+
+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)
+
+	// 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 [0,clientMaxPadLength] bytes of random padding.
+	//  * M_C is HMAC-SHA256(serverIdentity, X)
+	//  * MAC is HMAC-SHA256(serverIdentity, X .... E)
+	//  * E is the string representation of the number of hours since the UNIX
+	//    epoch.
+
+	// Generate the padding
+	pad, err := makePad(clientMinPadLength, clientMaxPadLength)
+	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))
+
+	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)
+	}
+
+	// Attempt to find the mark + MAC.
+	pos := findMark(hs.serverMark, resp,
+		ntor.RepresentativeLength+ntor.AuthLength, serverMaxHandshakeLength)
+	if pos == -1 {
+		if len(resp) >= serverMaxHandshakeLength {
+			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)
+	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
+
+	mac hash.Hash
+
+	clientRepresentative *ntor.Representative
+	clientMark           []byte
+}
+
+func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair) *serverHandshake {
+	hs := new(serverHandshake)
+	hs.nodeID = nodeID
+	hs.serverIdentity = serverIdentity
+	hs.mac = hmac.New(sha256.New, hs.serverIdentity.Public().Bytes()[:])
+
+	return hs
+}
+
+func (hs *serverHandshake) parseClientHandshake(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)
+	}
+
+	// Attempt to find the mark + MAC.
+	pos := findMark(hs.clientMark, resp, ntor.RepresentativeLength,
+		serverMaxHandshakeLength)
+	if pos == -1 {
+		if len(resp) >= clientMaxHandshakeLength {
+			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)
+		macRx := resp[pos+markLength : pos+markLength+macLength]
+		if hmac.Equal(macCmp, macRx) {
+			macFound = true
+			hs.epochHour = epochHour
+
+			// In theory, we should always evaluate all 3 MACs, but at this
+			// point we are reasonably confident that the client knows the
+			// correct NodeID/Public key, and if this fails, we just ignore the
+			// client for a random interval and drop the connection anyway.
+			break
+		}
+	}
+	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
+	}
+
+	// At this point the client knows that we exist, so do the keypair
+	// generation and complete our side of the handshake.
+	var err error
+	hs.keypair, err = ntor.NewKeypair(true)
+	if err != nil {
+		return nil, err
+	}
+
+	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)
+
+	// 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 [0,serverMaxPadLength] bytes of random padding.
+	//  * M_S is HMAC-SHA256(serverIdentity, Y)
+	//  * MAC is HMAC-SHA256(serverIdentity, Y .... E)
+	//  * E is the string representation of the number of hours since the UNIX
+	//    epoch.
+
+	// Generate the padding
+	pad, err := makePad(serverMinPadLength, serverMaxPadLength)
+	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))
+
+	return buf.Bytes(), nil
+}
+
+// getEpochHour returns the number of hours since the UNIX epoch.
+func getEpochHour() int64 {
+	return time.Now().Unix() / 3600
+}
+
+func findMark(mark, buf []byte, startPos, maxPos int) int {
+	endPos := len(buf)
+	if endPos > maxPos {
+		endPos = maxPos
+	}
+
+	// XXX: bytes.Index() uses a naive search, which kind of sucks.
+	pos := bytes.Index(buf[startPos:endPos], mark)
+	if pos == -1 {
+		return -1
+	}
+
+	// Return the index relative to the start of the slice.
+	return pos + startPos
+}
+
+func makePad(min, max int64) ([]byte, error) {
+	if max < min {
+		panic(fmt.Sprintf("makePad: min > max (%d, %d)", min, max))
+	}
+
+	padRange := int64((max + 1) - min)
+	padLen, err := rand.Int(rand.Reader, big.NewInt(padRange))
+	if err != nil {
+		return nil, err
+	}
+	pad := make([]byte, padLen.Int64()+min)
+	_, err = rand.Read(pad)
+	if err != nil {
+		return nil, err
+	}
+
+	return pad, err
+}
+
+/* vim :set ts=4 sw=4 sts=4 noet : */
diff --git a/handshake_ntor_test.go b/handshake_ntor_test.go
new file mode 100644
index 0000000..41780e9
--- /dev/null
+++ b/handshake_ntor_test.go
@@ -0,0 +1,80 @@
+/*
+ * 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/yawning/obfs4/ntor"
+)
+
+func TestHandshakeNtor(t *testing.T) {
+	// Generate the server node id and id keypair.
+	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)
+
+	// Intialize the client and server handshake states
+	clientHs, err := newClientHandshake(nodeID, idKeypair.Public())
+	if err != nil {
+		t.Fatal("newClientHandshake failed:", err)
+	}
+	serverHs := newServerHandshake(nodeID, idKeypair)
+
+	// Generate what the client will send to the server.
+	cToS, err := clientHs.generateHandshake()
+	if err != nil {
+		t.Fatal("clientHandshake.generateHandshake() failed", err)
+	}
+
+	// Parse the client handshake message.
+	serverSeed, err := serverHs.parseClientHandshake(cToS)
+	if err != nil {
+		t.Fatal("serverHandshake.parseClientHandshake() failed", err)
+	}
+
+	// Genrate what the server will send to the client.
+	sToC, err := serverHs.generateHandshake()
+	if err != nil {
+		t.Fatal("serverHandshake.generateHandshake() failed", err)
+	}
+
+	// Parse the server handshake message.
+	n, clientSeed, err := clientHs.parseServerHandshake(sToC)
+	if err != nil {
+		t.Fatal("clientHandshake.parseServerHandshake() failed", err)
+	}
+	if n != len(sToC) {
+		t.Fatalf("clientHandshake.parseServerHandshake() has bytes remaining: %d", n)
+	}
+
+	// Ensure the derived shared secret is the same.
+	if 0 != bytes.Compare(clientSeed, serverSeed) {
+		t.Fatalf("client/server seed mismatch")
+	}
+}
diff --git a/ntor/ntor.go b/ntor/ntor.go
new file mode 100644
index 0000000..da6dae4
--- /dev/null
+++ b/ntor/ntor.go
@@ -0,0 +1,435 @@
+/*
+ * 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 ntor implements the Tor Project's ntor handshake as defined in
+// proposal 216 "Improved circuit-creation key exchange".  It also supports
+// using Elligator to transform the Curve25519 public keys sent over the wire
+// to a form that is indistinguishable from random strings.
+//
+// Before using this package, it is strongly recommended that the specification
+// is read and understood.
+//
+package ntor
+
+import (
+	"bytes"
+	"crypto/hmac"
+	"crypto/rand"
+	"crypto/sha256"
+	"crypto/subtle"
+	"encoding/base64"
+	"fmt"
+	"io"
+
+	"code.google.com/p/go.crypto/curve25519"
+	"code.google.com/p/go.crypto/hkdf"
+
+	"github.com/agl/ed25519/extra25519"
+)
+
+const (
+	// PublicKeyLength is the length of a Curve25519 public key.
+	PublicKeyLength = 32
+
+	// RepresentativeLength is the length of an Elligator representative.
+	RepresentativeLength = 32
+
+	// PrivateKeyLength is the length of a Curve25519 private key.
+	PrivateKeyLength = 32
+
+	// SharedSecretLength is the length of a Curve25519 shared secret.
+	SharedSecretLength = 32
+
+	// NodeIDLength is the length of a ntor node identifier.
+	NodeIDLength = 20
+
+	// KeySeedLength is the length of the derived KEY_SEED.
+	KeySeedLength = sha256.Size
+
+	// AuthLength is the lenght of the derived AUTH.
+	AuthLength = sha256.Size
+)
+
+var protoID = []byte("ntor-curve25519-sha256-1")
+var tMac = append(protoID, []byte(":mac")...)
+var tKey = append(protoID, []byte(":key_extract")...)
+var tVerify = append(protoID, []byte(":key_verify")...)
+var mExpand = append(protoID, []byte(":key_expand")...)
+
+// PublicKeyLengthError is the error returned when the public key being
+// imported is an invalid length.
+type PublicKeyLengthError int
+
+func (e PublicKeyLengthError) Error() string {
+	return fmt.Sprintf("ntor: Invalid Curve25519 public key length: %d",
+		int(e))
+}
+
+// PrivateKeyLengthError is the error returned when the private key being
+// imported is an invalid length.
+type PrivateKeyLengthError int
+
+func (e PrivateKeyLengthError) Error() string {
+	return fmt.Sprintf("ntor: Invalid Curve25519 private key length: %d",
+		int(e))
+}
+
+// NodeIDLengthError is the error returned when the node ID being imported is
+// an invalid length.
+type NodeIDLengthError int
+
+func (e NodeIDLengthError) Error() string {
+	return fmt.Sprintf("ntor: Invalid NodeID length: %d", int(e))
+}
+
+// KeySeed is the key material that results from a handshake (KEY_SEED).
+type KeySeed [KeySeedLength]byte
+
+// Bytes returns a pointer to the raw key material.
+func (key_seed *KeySeed) Bytes() *[KeySeedLength]byte {
+	return (*[KeySeedLength]byte)(key_seed)
+}
+
+// Auth is the verifier that results from a handshake (AUTH).
+type Auth [AuthLength]byte
+
+// Bytes returns a pointer to the raw auth.
+func (auth *Auth) Bytes() *[AuthLength]byte {
+	return (*[AuthLength]byte)(auth)
+}
+
+// NodeID is a ntor node identifier.
+type NodeID [NodeIDLength]byte
+
+// NewNodeID creates a NodeID from the raw bytes.
+func NewNodeID(raw []byte) (*NodeID, error) {
+	if len(raw) != NodeIDLength {
+		return nil, NodeIDLengthError(len(raw))
+	}
+
+	nodeID := new(NodeID)
+	copy(nodeID[:], raw)
+
+	return nodeID, nil
+}
+
+// NodeIDFromBase64 creates a new NodeID from the Base64 encoded representation.
+func NodeIDFromBase64(encoded string) (*NodeID, error) {
+	raw, err := base64.StdEncoding.DecodeString(encoded)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewNodeID(raw)
+}
+
+// Base64 returns the Base64 representation of the NodeID.
+func (id *NodeID) Base64() string {
+	return base64.StdEncoding.EncodeToString(id[:])
+}
+
+// PublicKey is a Curve25519 public key in little-endian byte order.
+type PublicKey [PublicKeyLength]byte
+
+// Bytes returns a pointer to the raw Curve25519 public key.
+func (public *PublicKey) Bytes() *[PublicKeyLength]byte {
+	return (*[PublicKeyLength]byte)(public)
+}
+
+// Base64 returns the Base64 representation of the Curve25519 public key.
+func (public *PublicKey) Base64() string {
+	return base64.StdEncoding.EncodeToString(public.Bytes()[:])
+}
+
+// NewPublicKey creates a PublicKey from the raw bytes.
+func NewPublicKey(raw []byte) (*PublicKey, error) {
+	if len(raw) != PublicKeyLength {
+		return nil, PublicKeyLengthError(len(raw))
+	}
+
+	pubKey := new(PublicKey)
+	copy(pubKey[:], raw)
+
+	return pubKey, nil
+}
+
+// PublicKeyFromBase64 returns a PublicKey from a Base64 representation.
+func PublicKeyFromBase64(encoded string) (*PublicKey, error) {
+	raw, err := base64.StdEncoding.DecodeString(encoded)
+	if err != nil {
+		return nil, err
+	}
+
+	return NewPublicKey(raw)
+}
+
+// Representative is an Elligator representative of a Curve25519 public key
+// in little-endian byte order.
+type Representative [RepresentativeLength]byte
+
+// Bytes returns a pointer to the raw Elligator representative.
+func (repr *Representative) Bytes() *[RepresentativeLength]byte {
+	return (*[RepresentativeLength]byte)(repr)
+}
+
+// ToPublic converts a Elligator representative to a Curve25519 public key.
+func (repr *Representative) ToPublic() *PublicKey {
+	pub := new(PublicKey)
+
+	extra25519.RepresentativeToPublicKey(pub.Bytes(), repr.Bytes())
+	return pub
+}
+
+// PrivateKey is a Curve25519 private key in little-endian byte order.
+type PrivateKey [PrivateKeyLength]byte
+
+// Bytes returns a pointer to the raw Curve25519 private key.
+func (private *PrivateKey) Bytes() *[PrivateKeyLength]byte {
+	return (*[PrivateKeyLength]byte)(private)
+}
+
+// Base64 returns the Base64 representation of the Curve25519 private key.
+func (private *PrivateKey) Base64() string {
+	return base64.StdEncoding.EncodeToString(private.Bytes()[:])
+}
+
+// Keypair is a Curve25519 keypair with an optional Elligator representative.
+// As only certain Curve25519 keys can be obfuscated with Elligator, the
+// representative must be generated along with the keypair.
+type Keypair struct {
+	public         *PublicKey
+	private        *PrivateKey
+	representative *Representative
+}
+
+// Public returns the Curve25519 public key belonging to the Keypair.
+func (keypair *Keypair) Public() *PublicKey {
+	return keypair.public
+}
+
+// Private returns the Curve25519 private key belonging to the Keypair.
+func (keypair *Keypair) Private() *PrivateKey {
+	return keypair.private
+}
+
+// Representative returns the Elligator representative of the public key
+// belonging to the Keypair.
+func (keypair *Keypair) Representative() *Representative {
+	return keypair.representative
+}
+
+// HasElligator returns true if the Keypair has an Elligator representative.
+func (keypair *Keypair) HasElligator() bool {
+	return nil != keypair.representative
+}
+
+// NewKeypair generates a new Curve25519 keypair, and optionally also generates
+// an Elligator representative of the public key.
+func NewKeypair(elligator bool) (*Keypair, error) {
+	keypair := new(Keypair)
+	keypair.private = new(PrivateKey)
+	keypair.public = new(PublicKey)
+	if elligator {
+		keypair.representative = new(Representative)
+	}
+
+	for {
+		// Generate a Curve25519 private key.  Like everyone who does this,
+		// run the CSPRNG output through SHA256 for extra tinfoil hattery.
+		priv := keypair.private.Bytes()[:]
+		_, err := rand.Read(priv)
+		if err != nil {
+			return nil, err
+		}
+		digest := sha256.Sum256(priv)
+		digest[0] &= 248
+		digest[31] &= 127
+		digest[31] |= 64
+		copy(priv, digest[:])
+
+		if elligator {
+			// Apply the Elligator transform.  This fails ~50% of the time.
+			if !extra25519.ScalarBaseMult(keypair.public.Bytes(),
+				keypair.representative.Bytes(),
+				keypair.private.Bytes()) {
+				continue
+			}
+		} else {
+			// Generate the corresponding Curve25519 public key.
+			curve25519.ScalarBaseMult(keypair.public.Bytes(),
+				keypair.private.Bytes())
+		}
+
+		return keypair, nil
+	}
+}
+
+// LoadKeypair takes an existing Curve25519 private key from a buffer and
+// creates a Keypair including the public key.
+
+// KeypairFromBase64 returns a Keypair from a Base64 representation of the
+// private key.
+func KeypairFromBase64(encoded string) (*Keypair, error) {
+	raw, err := base64.StdEncoding.DecodeString(encoded)
+	if err != nil {
+		return nil, err
+	}
+
+	if len(raw) != PrivateKeyLength {
+		return nil, PrivateKeyLengthError(len(raw))
+	}
+
+	keypair := new(Keypair)
+	keypair.private = new(PrivateKey)
+	keypair.public = new(PublicKey)
+
+	copy(keypair.private[:], raw)
+	curve25519.ScalarBaseMult(keypair.public.Bytes(),
+		keypair.private.Bytes())
+
+	return keypair, nil
+}
+
+// ServerHandshake does the server side of a ntor handshake and returns status,
+// KEY_SEED, and AUTH.  If status is not true, the handshake MUST be aborted.
+func ServerHandshake(clientPublic *PublicKey, serverKeypair *Keypair, idKeypair *Keypair, id *NodeID) (bool, *KeySeed, *Auth) {
+	var notOk int
+	var secretInput bytes.Buffer
+
+	// Server side uses EXP(X,y) | EXP(X,b)
+	var exp [SharedSecretLength]byte
+	curve25519.ScalarMult(&exp, serverKeypair.private.Bytes(),
+		clientPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	curve25519.ScalarMult(&exp, idKeypair.private.Bytes(),
+		clientPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	keySeed, auth := ntorCommon(secretInput, id, idKeypair.public,
+		clientPublic, serverKeypair.public)
+	return notOk == 0, keySeed, auth
+}
+
+// ClientHandshake does the client side of a ntor handshake and returnes
+// status, KEY_SEED, and AUTH.  If status is not true or AUTH does not match
+// the value recieved from the server, the handshake MUST be aborted.
+func ClientHandshake(clientKeypair *Keypair, serverPublic *PublicKey, idPublic *PublicKey, id *NodeID) (bool, *KeySeed, *Auth) {
+	var notOk int
+	var secretInput bytes.Buffer
+
+	// Client side uses EXP(Y,x) | EXP(B,x)
+	var exp [SharedSecretLength]byte
+	curve25519.ScalarMult(&exp, clientKeypair.private.Bytes(),
+		serverPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	curve25519.ScalarMult(&exp, clientKeypair.private.Bytes(),
+		idPublic.Bytes())
+	notOk |= constantTimeIsZero(exp[:])
+	secretInput.Write(exp[:])
+
+	keySeed, auth := ntorCommon(secretInput, id, idPublic,
+		clientKeypair.public, serverPublic)
+	return notOk == 0, keySeed, auth
+}
+
+// CompareAuth does a constant time compare of a Auth and a byte slice
+// (presumably received over a network).
+func CompareAuth(auth1 *Auth, auth2 []byte) bool {
+	auth1Bytes := auth1.Bytes()
+	return hmac.Equal(auth1Bytes[:], auth2)
+}
+
+func ntorCommon(secretInput bytes.Buffer, id *NodeID, b *PublicKey, x *PublicKey, y *PublicKey) (*KeySeed, *Auth) {
+	keySeed := new(KeySeed)
+	auth := new(Auth)
+
+	// secret_input/auth_input use this common bit, build it once.
+	suffix := bytes.NewBuffer(b.Bytes()[:])
+	suffix.Write(b.Bytes()[:])
+	suffix.Write(x.Bytes()[:])
+	suffix.Write(y.Bytes()[:])
+	suffix.Write(protoID)
+	suffix.Write(id[:])
+
+	// At this point secret_input has the 2 exponents, concatenated, append the
+	// client/server common suffix.
+	secretInput.Write(suffix.Bytes())
+
+	// KEY_SEED = H(secret_input, t_key)
+	h := hmac.New(sha256.New, tKey)
+	h.Write(secretInput.Bytes())
+	tmp := h.Sum(nil)
+	copy(keySeed[:], tmp)
+
+	// verify = H(secret_input, t_verify)
+	h = hmac.New(sha256.New, tVerify)
+	h.Write(secretInput.Bytes())
+	verify := h.Sum(nil)
+
+	// auth_input = verify | ID | B | Y | X | PROTOID | "Server"
+	authInput := bytes.NewBuffer(verify)
+	authInput.Write(suffix.Bytes())
+	authInput.Write([]byte("Server"))
+	h = hmac.New(sha256.New, tMac)
+	h.Write(authInput.Bytes())
+	tmp = h.Sum(nil)
+	copy(auth[:], tmp)
+
+	return keySeed, auth
+}
+
+func constantTimeIsZero(x []byte) int {
+	var ret byte
+	for _, v := range x {
+		ret |= v
+	}
+
+	return subtle.ConstantTimeByteEq(ret, 0)
+}
+
+// Kdf extracts and expands KEY_SEED via HKDF-SHA256 and returns `okm_len` bytes
+// of key material.
+func Kdf(keySeed []byte, okmLen int) []byte {
+	kdf := hkdf.New(sha256.New, keySeed, tKey, mExpand)
+	okm := make([]byte, okmLen)
+	n, err := io.ReadFull(kdf, okm)
+	if err != nil {
+		panic(fmt.Sprintf("BUG: Failed HKDF: %s", err.Error()))
+	} else if n != len(okm) {
+		panic(fmt.Sprintf("BUG: Got truncated HKDF output: %d", n))
+	}
+
+	return okm
+}
+
+/* vim :set ts=4 sw=4 sts=4 noet : */
diff --git a/ntor/ntor_test.go b/ntor/ntor_test.go
new file mode 100644
index 0000000..9d7c687
--- /dev/null
+++ b/ntor/ntor_test.go
@@ -0,0 +1,182 @@
+/*
+ * 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 ntor
+
+import (
+	"bytes"
+	"testing"
+)
+
+// TestNewKeypair tests Curve25519/Elligator keypair generation.
+func TestNewKeypair(t *testing.T) {
+	// Test standard Curve25519 first.
+	keypair, err := NewKeypair(false)
+	if err != nil {
+		t.Fatal("NewKeypair(false) failed:", err)
+	}
+	if keypair == nil {
+		t.Fatal("NewKeypair(false) returned nil")
+	}
+	if keypair.HasElligator() {
+		t.Fatal("NewKeypair(false) has a Elligator representative")
+	}
+
+	// Test Elligator generation.
+	keypair, err = NewKeypair(true)
+	if err != nil {
+		t.Fatal("NewKeypair(true) failed:", err)
+	}
+	if keypair == nil {
+		t.Fatal("NewKeypair(true) returned nil")
+	}
+	if !keypair.HasElligator() {
+		t.Fatal("NewKeypair(true) mising an Elligator representative")
+	}
+}
+
+// Test Client/Server handshake.
+func TestHandshake(t *testing.T) {
+	clientKeypair, err := NewKeypair(true)
+	if err != nil {
+		t.Fatal("Failed to generate client keypair:", err)
+	}
+	if clientKeypair == nil {
+		t.Fatal("Client keypair is nil")
+	}
+
+	serverKeypair, err := NewKeypair(true)
+	if err != nil {
+		t.Fatal("Failed to generate server keypair:", err)
+	}
+	if serverKeypair == nil {
+		t.Fatal("Server keypair is nil")
+	}
+
+	idKeypair, err := NewKeypair(false)
+	if err != nil {
+		t.Fatal("Failed to generate identity keypair:", err)
+	}
+	if idKeypair == nil {
+		t.Fatal("Identity keypair is nil")
+	}
+
+	nodeID, err := NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"))
+	if err != nil {
+		t.Fatal("Failed to load NodeId:", err)
+	}
+
+	// ServerHandshake
+	clientPublic := clientKeypair.Representative().ToPublic()
+	ok, serverSeed, serverAuth := ServerHandshake(clientPublic,
+		serverKeypair, idKeypair, nodeID)
+	if !ok {
+		t.Fatal("ServerHandshake failed")
+	}
+	if serverSeed == nil {
+		t.Fatal("ServerHandshake returned nil KEY_SEED")
+	}
+	if serverAuth == nil {
+		t.Fatal("ServerHandshake returned nil AUTH")
+	}
+
+	// ClientHandshake
+	ok, clientSeed, clientAuth := ClientHandshake(clientKeypair,
+		serverKeypair.Public(), idKeypair.Public(), nodeID)
+	if !ok {
+		t.Fatal("ClientHandshake failed")
+	}
+	if clientSeed == nil {
+		t.Fatal("ClientHandshake returned nil KEY_SEED")
+	}
+	if clientAuth == nil {
+		t.Fatal("ClientHandshake returned nil AUTH")
+	}
+
+	// WARNING: Use a constant time comparison in actual code.
+	if 0 != bytes.Compare(clientSeed.Bytes()[:], serverSeed.Bytes()[:]) {
+		t.Fatal("KEY_SEED mismatched between client/server")
+	}
+	if 0 != bytes.Compare(clientAuth.Bytes()[:], serverAuth.Bytes()[:]) {
+		t.Fatal("AUTH mismatched between client/server")
+	}
+}
+
+// Benchmark Client/Server handshake.  The actual time taken that will be
+// observed on either the Client or Server is half the reported time per
+// operation since the benchmark does both sides.
+func BenchmarkHandshake(b *testing.B) {
+	// Generate the "long lasting" identity key and NodeId.
+	idKeypair, err := NewKeypair(false)
+	if err != nil || idKeypair == nil {
+		b.Fatal("Failed to generate identity keypair")
+	}
+	nodeID, err := NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"))
+	if err != nil {
+		b.Fatal("Failed to load NodeId:", err)
+	}
+	b.ResetTimer()
+
+	// Start the actual benchmark.
+	for i := 0; i < b.N; i++ {
+		// Generate the keypairs.
+		serverKeypair, err := NewKeypair(true)
+		if err != nil || serverKeypair == nil {
+			b.Fatal("Failed to generate server keypair")
+		}
+
+		clientKeypair, err := NewKeypair(true)
+		if err != nil || clientKeypair == nil {
+			b.Fatal("Failed to generate client keypair")
+		}
+
+		// Server handshake.
+		clientPublic := clientKeypair.Representative().ToPublic()
+		ok, serverSeed, serverAuth := ServerHandshake(clientPublic,
+			serverKeypair, idKeypair, nodeID)
+		if !ok || serverSeed == nil || serverAuth == nil {
+			b.Fatal("ServerHandshake failed")
+		}
+
+		// Client handshake.
+		serverPublic := serverKeypair.Representative().ToPublic()
+		ok, clientSeed, clientAuth := ClientHandshake(clientKeypair,
+			serverPublic, idKeypair.Public(), nodeID)
+		if !ok || clientSeed == nil || clientAuth == nil {
+			b.Fatal("ClientHandshake failed")
+		}
+
+		// Validate the authenticator.  Real code would pass the AUTH read off
+		// the network as a slice to CompareAuth here.
+		if !CompareAuth(clientAuth, serverAuth.Bytes()[:]) ||
+			!CompareAuth(serverAuth, clientAuth.Bytes()[:]) {
+			b.Fatal("AUTH mismatched between client/server")
+		}
+	}
+}
+
+/* vim :set ts=4 sw=4 sts=4 noet : */
diff --git a/obfs4-client/obfs4-client.go b/obfs4-client/obfs4-client.go
new file mode 100644
index 0000000..077fb85
--- /dev/null
+++ b/obfs4-client/obfs4-client.go
@@ -0,0 +1,183 @@
+/*
+ * 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.
+ *
+ * This file is based off goptlib's dummy-client.go file.
+ */
+
+// obfs4 pluggable transport client. Works only as a managed proxy.
+//
+// Usage (in torrc):
+// 	UseBridges 1
+// 	Bridge obfs4 X.X.X.X:YYYY public-key=<Base64 Bridge public key> node-id=<Base64 Node ID>
+// 	ClientTransportPlugin obfs4 exec obfs4-client
+//
+// Becuase the pluggable transport requires arguments, using obfs4-client 
+// requires tor 0.2.5.x.
+package main
+
+import (
+	"io"
+	"net"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+
+	"github.com/yawning/obfs4"
+)
+
+import "git.torproject.org/pluggable-transports/goptlib.git"
+
+var ptInfo pt.ClientInfo
+
+// When a connection handler starts, +1 is written to this channel; when it
+// ends, -1 is written.
+var handlerChan = make(chan int)
+
+func copyLoop(a, b net.Conn) {
+	var wg sync.WaitGroup
+	wg.Add(2)
+
+	// TODO: Log errors.
+	go func() {
+		io.Copy(b, a)
+		wg.Done()
+	}()
+	go func() {
+		io.Copy(a, b)
+		wg.Done()
+	}()
+
+	wg.Wait()
+}
+
+func handler(conn *pt.SocksConn) error {
+	// Extract the peer's node ID and public key.
+	nodeID, ok := conn.Req.Args.Get("node-id")
+	if !ok {
+		// TODO: Log something here.
+		conn.Reject()
+	}
+	publicKey, ok := conn.Req.Args.Get("public-key")
+	if !ok {
+		// TODO: Log something here.
+		conn.Reject()
+	}
+
+	handlerChan <- 1
+	defer func() {
+		handlerChan <- -1
+	}()
+
+	defer conn.Close()
+	remote, err := obfs4.Dial("tcp", conn.Req.Target, nodeID, publicKey)
+	if err != nil {
+		conn.Reject()
+		return err
+	}
+	defer remote.Close()
+	err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
+	if err != nil {
+		return err
+	}
+
+	copyLoop(conn, remote)
+
+	return nil
+}
+
+func acceptLoop(ln *pt.SocksListener) error {
+	defer ln.Close()
+	for {
+		conn, err := ln.AcceptSocks()
+		if err != nil {
+			if e, ok := err.(net.Error); ok && !e.Temporary() {
+				return err
+			}
+			continue
+		}
+		go handler(conn)
+	}
+}
+
+func main() {
+	var err error
+
+	ptInfo, err = pt.ClientSetup([]string{"obfs4"})
+	if err != nil {
+		os.Exit(1)
+	}
+
+	listeners := make([]net.Listener, 0)
+	for _, methodName := range ptInfo.MethodNames {
+		switch methodName {
+		case "obfs4":
+			ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
+			if err != nil {
+				pt.CmethodError(methodName, err.Error())
+				break
+			}
+			go acceptLoop(ln)
+			pt.Cmethod(methodName, ln.Version(), ln.Addr())
+			listeners = append(listeners, ln)
+		default:
+			pt.CmethodError(methodName, "no such method")
+		}
+	}
+	pt.CmethodsDone()
+
+	var numHandlers int = 0
+	var sig os.Signal
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+	// wait for first signal
+	sig = nil
+	for sig == nil {
+		select {
+		case n := <-handlerChan:
+			numHandlers += n
+		case sig = <-sigChan:
+		}
+	}
+	for _, ln := range listeners {
+		ln.Close()
+	}
+
+	if sig == syscall.SIGTERM {
+		return
+	}
+
+	// wait for second signal or no more handlers
+	sig = nil
+	for sig == nil && numHandlers != 0 {
+		select {
+		case n := <-handlerChan:
+			numHandlers += n
+		case sig = <-sigChan:
+		}
+	}
+}
diff --git a/obfs4-server/obfs4-server.go b/obfs4-server/obfs4-server.go
new file mode 100644
index 0000000..aac2351
--- /dev/null
+++ b/obfs4-server/obfs4-server.go
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ *
+ * This file is based off goptlib's dummy-server.go file.
+ */
+
+// obfs4 pluggable transport server. Works only as a managed proxy.
+//
+// Usage (in torrc):
+// 	BridgeRelay 1
+// 	ORPort 9001
+// 	ExtORPort 6669
+// 	ServerTransportPlugin obfs4 exec obfs4-server
+//  ServerTransportOptions obfs4 private-key=<Base64 Bridge private key> node-id=<Base64 Node ID>
+//
+// Becuase the pluggable transport requires arguments, using obfs4-server
+// requires tor 0.2.5.x.
+package main
+
+import (
+	"encoding/hex"
+	"flag"
+	"fmt"
+	"io"
+	"net"
+	"os"
+	"os/signal"
+	"sync"
+	"syscall"
+
+	"github.com/yawning/obfs4"
+	"github.com/yawning/obfs4/ntor"
+)
+
+import "git.torproject.org/pluggable-transports/goptlib.git"
+
+var ptInfo pt.ServerInfo
+
+// When a connection handler starts, +1 is written to this channel; when it
+// ends, -1 is written.
+var handlerChan = make(chan int)
+
+func copyLoop(a, b net.Conn) {
+	var wg sync.WaitGroup
+	wg.Add(2)
+
+	go func() {
+		io.Copy(b, a)
+		wg.Done()
+	}()
+	go func() {
+		io.Copy(a, b)
+		wg.Done()
+	}()
+
+	wg.Wait()
+}
+
+func handler(conn net.Conn) error {
+	defer conn.Close()
+
+	handlerChan <- 1
+	defer func() {
+		handlerChan <- -1
+	}()
+
+	or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "obfs4")
+	if err != nil {
+		return err
+	}
+	defer or.Close()
+
+	copyLoop(conn, or)
+
+	return nil
+}
+
+func acceptLoop(ln net.Listener) error {
+	defer ln.Close()
+	for {
+		conn, err := ln.Accept()
+		if err != nil {
+			if e, ok := err.(net.Error); ok && !e.Temporary() {
+				return err
+			}
+			continue
+		}
+		go handler(conn)
+	}
+}
+
+func generateParams(id string) {
+	rawID, err := hex.DecodeString(id)
+	if err != nil {
+		fmt.Println("Failed to hex decode id:", err)
+		return
+	}
+
+	parsedID, err := ntor.NewNodeID(rawID)
+	if err != nil {
+		fmt.Println("Failed to parse id:", err)
+		return
+	}
+
+	fmt.Println("Generated node_id:", parsedID.Base64())
+
+	keypair, err := ntor.NewKeypair(false)
+	if err != nil {
+		fmt.Println("Failed to generate keypair:", err)
+		return
+	}
+
+	fmt.Println("Generated private-key:", keypair.Private().Base64())
+	fmt.Println("Generated public-key:", keypair.Public().Base64())
+}
+
+func main() {
+	var err error
+
+	// Some command line args.
+	genParams := flag.String("gen", "", "Generate params given a Node ID.")
+	flag.Parse()
+	if *genParams != "" {
+		generateParams(*genParams)
+		os.Exit(0)
+	}
+
+	// Ok, guess we're in PT land.
+	ptInfo, err = pt.ServerSetup([]string{"obfs4"})
+	if err != nil {
+		os.Exit(1)
+	}
+
+	listeners := make([]net.Listener, 0)
+	for _, bindaddr := range ptInfo.Bindaddrs {
+		switch bindaddr.MethodName {
+		case "obfs4":
+			// Handle the mandetory arguments.
+			privateKey, ok := bindaddr.Options.Get("private-key")
+			if !ok {
+				pt.SmethodError(bindaddr.MethodName, "need a private-key option")
+				break
+			}
+			nodeID, ok := bindaddr.Options.Get("node-id")
+			if !ok {
+				pt.SmethodError(bindaddr.MethodName, "need a node-id option")
+				break
+			}
+
+			ln, err := obfs4.Listen("tcp", bindaddr.Addr.String(), nodeID,
+									privateKey)
+			if err != nil {
+				pt.SmethodError(bindaddr.MethodName, err.Error())
+				break
+			}
+
+			oLn, _ := ln.(*obfs4.Obfs4Listener)
+			args := pt.Args{}
+			args.Add("node-id", nodeID)
+			args.Add("public-key", oLn.PublicKey())
+			go acceptLoop(ln)
+			pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args)
+			// TODO: Maybe log the args?
+			listeners = append(listeners, ln)
+		default:
+			pt.SmethodError(bindaddr.MethodName, "no such method")
+		}
+	}
+	pt.SmethodsDone()
+
+	var numHandlers int = 0
+	var sig os.Signal
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+
+	// wait for first signal
+	sig = nil
+	for sig == nil {
+		select {
+		case n := <-handlerChan:
+			numHandlers += n
+		case sig = <-sigChan:
+		}
+	}
+	for _, ln := range listeners {
+		ln.Close()
+	}
+
+	if sig == syscall.SIGTERM {
+		return
+	}
+
+	// wait for second signal or no more handlers
+	sig = nil
+	for sig == nil && numHandlers != 0 {
+		select {
+		case n := <-handlerChan:
+			numHandlers += n
+		case sig = <-sigChan:
+		}
+	}
+}
diff --git a/obfs4.go b/obfs4.go
new file mode 100644
index 0000000..afb0716
--- /dev/null
+++ b/obfs4.go
@@ -0,0 +1,399 @@
+/*
+ * 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 implements the obfs4 protocol.
+package obfs4
+
+import (
+	"bytes"
+	"net"
+	"syscall"
+	"time"
+
+	"github.com/yawning/obfs4/framing"
+	"github.com/yawning/obfs4/ntor"
+)
+
+const (
+	defaultReadSize = framing.MaximumSegmentLength
+)
+
+// Obfs4Conn is the implementation of the net.Conn interface for obfs4
+// connections.
+type Obfs4Conn struct {
+	conn net.Conn
+
+	encoder *framing.Encoder
+	decoder *framing.Decoder
+
+	receiveBuffer        bytes.Buffer
+	receiveDecodedBuffer bytes.Buffer
+
+	isOk bool
+}
+
+func (c *Obfs4Conn) clientHandshake(nodeID *ntor.NodeID, publicKey *ntor.PublicKey) error {
+	// Generate/send the client handshake.
+	hs, err := newClientHandshake(nodeID, publicKey)
+	if err != nil {
+		return err
+	}
+	blob, err := hs.generateHandshake()
+	if err != nil {
+		return err
+	}
+	_, err = c.conn.Write(blob)
+	if err != nil {
+		return err
+	}
+
+	// XXX: Set the response timer.
+
+	// Consume the server handshake.
+	hsBuf := make([]byte, serverMaxHandshakeLength)
+	for {
+		n, err := c.conn.Read(hsBuf)
+		if err != nil {
+			return err
+		}
+		c.receiveBuffer.Write(hsBuf[:n])
+
+		n, seed, err := hs.parseServerHandshake(c.receiveBuffer.Bytes())
+		if err == ErrMarkNotFoundYet {
+			continue
+		} else if err != nil {
+			return err
+		}
+		_ = c.receiveBuffer.Next(n)
+
+		// Use the derived key material to intialize the link crypto.
+		okm := ntor.Kdf(seed, framing.KeyLength*2)
+		c.encoder = framing.NewEncoder(okm[:framing.KeyLength])
+		c.decoder = framing.NewDecoder(okm[framing.KeyLength:])
+
+		// XXX: Kill the response timer.
+		c.isOk = true
+
+		return nil
+	}
+}
+
+func (c *Obfs4Conn) serverHandshake(nodeID *ntor.NodeID, keypair *ntor.Keypair) error {
+	hs := newServerHandshake(nodeID, keypair)
+
+	// XXX: Set the request timer.
+
+	// Consume the client handshake.
+	hsBuf := make([]byte, clientMaxHandshakeLength)
+	for {
+		n, err := c.conn.Read(hsBuf)
+		if err != nil {
+			return err
+		}
+		c.receiveBuffer.Write(hsBuf[:n])
+
+		seed, err := hs.parseClientHandshake(c.receiveBuffer.Bytes())
+		if err == ErrMarkNotFoundYet {
+			continue
+		} else if err != nil {
+			return err
+		}
+		c.receiveBuffer.Reset()
+
+		// Use the derived key material to intialize the link crypto.
+		okm := ntor.Kdf(seed, framing.KeyLength*2)
+		c.encoder = framing.NewEncoder(okm[framing.KeyLength:])
+		c.decoder = framing.NewDecoder(okm[:framing.KeyLength])
+
+		// XXX: Kill the request timer.
+
+		break
+	}
+
+	// Generate/send the response.
+	blob, err := hs.generateHandshake()
+	if err != nil {
+		return err
+	}
+	_, err = c.conn.Write(blob)
+	if err != nil {
+		return err
+	}
+
+	c.isOk = true
+
+	return nil
+}
+
+func (c *Obfs4Conn) Read(b []byte) (int, error) {
+	if !c.isOk {
+		return 0, syscall.EINVAL
+	}
+
+	if c.receiveDecodedBuffer.Len() > 0 {
+		n, err := c.receiveDecodedBuffer.Read(b)
+		return n, err
+	}
+
+	// Consume and decode frames off the network.
+	buf := make([]byte, defaultReadSize)
+	for c.receiveDecodedBuffer.Len() == 0 {
+		n, err := c.conn.Read(buf)
+		if err != nil {
+			return 0, err
+		}
+		c.receiveBuffer.Write(buf[:n])
+
+		// Decode the data just read.
+		for c.receiveBuffer.Len() > 0 {
+			_, frame, err := c.decoder.Decode(&c.receiveBuffer)
+			if err == framing.ErrAgain {
+				break
+			} else if err != nil {
+				// Any non-timeout frame decoder errors are fatal.
+				if neterr, ok := err.(net.Error); ok && !neterr.Timeout() {
+					c.isOk = false
+				}
+				return 0, err
+			}
+
+			// TODO: Support more than raw payload directly in NaCl boxes.
+
+			c.receiveDecodedBuffer.Write(frame)
+		}
+	}
+
+	n, err := c.receiveDecodedBuffer.Read(b)
+	return n, err
+}
+
+func (c *Obfs4Conn) Write(b []byte) (int, error) {
+	chopBuf := bytes.NewBuffer(b)
+	buf := make([]byte, framing.MaximumFramePayloadLength)
+	nSent := 0
+	var frameBuf bytes.Buffer
+
+	for chopBuf.Len() > 0 {
+		// TODO: Support randomly padding frames.
+
+		// Send maximum sized frames.
+		n, err := chopBuf.Read(buf)
+		if err != nil {
+			return nSent, err
+		} else if n == 0 {
+			panic("Write(), chopping lenght was 0")
+		}
+
+		// Encode the frame.
+		_, frame, err := c.encoder.Encode(buf[:n])
+		if err != nil {
+			c.isOk = false
+			return nSent, err
+		}
+
+		_, err = frameBuf.Write(frame)
+		if err != nil {
+			c.isOk = false
+			return nSent, err
+		}
+
+		nSent += n
+	}
+
+	// Send the frame.
+	_, err := c.conn.Write(frameBuf.Bytes())
+	if err != nil {
+		// Non-timeout write errors as fatal.
+		if neterr, ok := err.(net.Error); ok && !neterr.Timeout() {
+			c.isOk = false
+		}
+		return nSent, err
+	}
+
+	return nSent, nil
+}
+
+func (c *Obfs4Conn) Close() error {
+	if c.conn == nil {
+		return syscall.EINVAL
+	}
+
+	return c.conn.Close()
+}
+
+func (c *Obfs4Conn) LocalAddr() net.Addr {
+	if !c.isOk {
+		return nil
+	}
+
+	return c.conn.LocalAddr()
+}
+
+func (c *Obfs4Conn) RemoteAddr() net.Addr {
+	if !c.isOk {
+		return nil
+	}
+
+	return c.conn.RemoteAddr()
+}
+
+func (c *Obfs4Conn) SetDeadline(t time.Time) error {
+	if !c.isOk {
+		return syscall.EINVAL
+	}
+
+	return c.conn.SetDeadline(t)
+}
+
+func (c *Obfs4Conn) SetReadDeadline(t time.Time) error {
+	if !c.isOk {
+		return syscall.EINVAL
+	}
+
+	return c.conn.SetReadDeadline(t)
+}
+
+func (c *Obfs4Conn) SetWriteDeadline(t time.Time) error {
+	if !c.isOk {
+		return syscall.EINVAL
+	}
+
+	return c.conn.SetWriteDeadline(t)
+}
+
+func Dial(network, address, nodeID, publicKey string) (net.Conn, error) {
+	// Decode the node_id/public_key.
+	pub, err := ntor.PublicKeyFromBase64(publicKey)
+	if err != nil {
+		return nil, err
+	}
+	id, err := ntor.NodeIDFromBase64(nodeID)
+	if err != nil {
+		return nil, err
+	}
+
+	// Connect to the peer.
+	c := new(Obfs4Conn)
+	c.conn, err = net.Dial(network, address)
+	if err != nil {
+		return nil, err
+	}
+
+	// Handshake.
+	err = c.clientHandshake(id, pub)
+	if err != nil {
+		c.conn.Close()
+		return nil, err
+	}
+
+	return c, nil
+}
+
+// Obfs4Listener a obfs4 network listener.  Clients should use variables of
+// type Listener instead of assuming obfs4.
+type Obfs4Listener struct {
+	listener net.Listener
+
+	keyPair *ntor.Keypair
+	nodeID  *ntor.NodeID
+}
+
+type ListenerError struct {
+	err error
+}
+
+func (e *ListenerError) Error() string {
+	return e.err.Error()
+}
+
+func (e *ListenerError) Temporary() bool {
+	return true
+}
+
+func (e *ListenerError) Timeout() bool {
+	return false
+}
+
+func (l *Obfs4Listener) Accept() (net.Conn, error) {
+	// Accept a connection.
+	c, err := l.listener.Accept()
+	if err != nil {
+		return nil, err
+	}
+
+	// Allocate the obfs4 connection state.
+	cObfs := new(Obfs4Conn)
+	cObfs.conn = c
+
+	// Complete the handshake.
+	err = cObfs.serverHandshake(l.nodeID, l.keyPair)
+	if err != nil {
+		// XXX: Close after a delay.
+		c.Close()
+		return nil, &ListenerError{err}
+	}
+
+	return cObfs, nil
+}
+
+func (l *Obfs4Listener) Close() error {
+	return l.listener.Close()
+}
+
+func (l *Obfs4Listener) Addr() net.Addr {
+	return l.listener.Addr()
+}
+
+func (l *Obfs4Listener) PublicKey() string {
+	if l.keyPair == nil {
+		return ""
+	}
+	return l.keyPair.Public().Base64()
+}
+
+func Listen(network, laddr, nodeID, privateKey string) (net.Listener, error) {
+	var err error
+
+	// Decode node_id/private_key.
+	l := new(Obfs4Listener)
+	l.keyPair, err = ntor.KeypairFromBase64(privateKey)
+	if err != nil {
+		return nil, err
+	}
+	l.nodeID, err = ntor.NodeIDFromBase64(nodeID)
+	if err != nil {
+		return nil, err
+	}
+
+	// Start up the listener.
+	l.listener, err = net.Listen(network, laddr)
+	if err != nil {
+		return nil, err
+	}
+
+	return l, nil
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/obfs4proxy.git



More information about the Pkg-privacy-commits mailing list