Initial import.

This commit is contained in:
Yawning Angel 2014-05-09 10:23:58 +00:00
commit ef38b844f9
11 changed files with 2430 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
*.swp
*~
obfs4-client/obfs4-client
obfs4-server/obfs4-server

54
README.md Normal file
View File

@ -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.

303
framing/framing.go Normal file
View File

@ -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 : */

178
framing/framing_test.go Normal file
View File

@ -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 : */

386
handshake_ntor.go Normal file
View File

@ -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 : */

80
handshake_ntor_test.go Normal file
View File

@ -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")
}
}

435
ntor/ntor.go Normal file
View File

@ -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 : */

182
ntor/ntor_test.go Normal file
View File

@ -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 : */

View File

@ -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:
}
}
}

View File

@ -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:
}
}
}

399
obfs4.go Normal file
View File

@ -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
}