mirror of https://gitlab.com/yawning/obfs4
Massive cleanup/code reorg.
* Changed obfs4proxy to be more like obfsproxy in terms of design, including being an easy framework for developing new TCP/IP style pluggable transports. * Added support for also acting as an obfs2/obfs3 client or bridge as a transition measure (and because the code itself is trivial). * Massively cleaned up the obfs4 and related code to be easier to read, and more idiomatic Go-like in style. * To ease deployment, obfs4proxy will now autogenerate the node-id, curve25519 keypair, and drbg seed if none are specified, and save them to a JSON file in the pt_state directory (Fixes Tor bug #12605).merge-requests/3/head
parent
8a3eb4b309
commit
339c63f0c8
@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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 replayfilter implements a generic replay detection filter with a
|
||||
// caller specifiable time-to-live. It only detects if a given byte sequence
|
||||
// has been seen before based on the SipHash-2-4 digest of the sequence.
|
||||
// Collisions are treated as positive matches, though the probability of this
|
||||
// happening is negligible.
|
||||
package replayfilter
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/siphash"
|
||||
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
|
||||
)
|
||||
|
||||
// maxFilterSize is the maximum capacity of a replay filter. This value is
|
||||
// more as a safeguard to prevent runaway filter growth, and is sized to be
|
||||
// serveral orders of magnitude greater than the number of connections a busy
|
||||
// bridge sees in one day, so in practice should never be reached.
|
||||
const maxFilterSize = 100 * 1024
|
||||
|
||||
type entry struct {
|
||||
digest uint64
|
||||
firstSeen time.Time
|
||||
element *list.Element
|
||||
}
|
||||
|
||||
// ReplayFilter is a simple filter designed only to detect if a given byte
|
||||
// sequence has been seen before.
|
||||
type ReplayFilter struct {
|
||||
sync.Mutex
|
||||
|
||||
filter map[uint64]*entry
|
||||
fifo *list.List
|
||||
|
||||
key [2]uint64
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
// New creates a new ReplayFilter instance.
|
||||
func New(ttl time.Duration) (filter *ReplayFilter, err error) {
|
||||
// Initialize the SipHash-2-4 instance with a random key.
|
||||
var key [16]byte
|
||||
if err = csrand.Bytes(key[:]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
filter = new(ReplayFilter)
|
||||
filter.filter = make(map[uint64]*entry)
|
||||
filter.fifo = list.New()
|
||||
filter.key[0] = binary.BigEndian.Uint64(key[0:8])
|
||||
filter.key[1] = binary.BigEndian.Uint64(key[8:16])
|
||||
filter.ttl = ttl
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// TestAndSet queries the filter for a given byte sequence, inserts the
|
||||
// sequence, and returns if it was present before the insertion operation.
|
||||
func (f *ReplayFilter) TestAndSet(now time.Time, buf []byte) bool {
|
||||
digest := siphash.Hash(f.key[0], f.key[1], buf)
|
||||
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.compactFilter(now)
|
||||
|
||||
if e := f.filter[digest]; e != nil {
|
||||
// Hit. Just return.
|
||||
return true
|
||||
}
|
||||
|
||||
// Miss. Add a new entry.
|
||||
e := new(entry)
|
||||
e.digest = digest
|
||||
e.firstSeen = now
|
||||
e.element = f.fifo.PushBack(e)
|
||||
f.filter[digest] = e
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *ReplayFilter) compactFilter(now time.Time) {
|
||||
e := f.fifo.Front()
|
||||
for e != nil {
|
||||
ent, _ := e.Value.(*entry)
|
||||
|
||||
// If the filter is not full, only purge entries that exceed the TTL,
|
||||
// otherwise purge at least one entry, then revert to TTL based
|
||||
// compaction.
|
||||
if f.fifo.Len() < maxFilterSize && f.ttl > 0 {
|
||||
deltaT := now.Sub(ent.firstSeen)
|
||||
if deltaT < 0 {
|
||||
// Aeeeeeee, the system time jumped backwards, potentially by
|
||||
// a lot. This will eventually self-correct, but "eventually"
|
||||
// could be a long time. As much as this sucks, jettison the
|
||||
// entire filter.
|
||||
f.reset()
|
||||
return
|
||||
} else if deltaT < f.ttl {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the eldest entry.
|
||||
eNext := e.Next()
|
||||
delete(f.filter, ent.digest)
|
||||
f.fifo.Remove(ent.element)
|
||||
ent.element = nil
|
||||
e = eNext
|
||||
}
|
||||
}
|
||||
|
||||
func (f *ReplayFilter) reset() {
|
||||
f.filter = make(map[uint64]*entry)
|
||||
f.fifo = list.New()
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
||||
// Package uniformdh implements the Tor Project's UniformDH key exchange
|
||||
// mechanism as defined in the obfs3 protocol specification. This
|
||||
// implementation is suitable for obfuscation but MUST NOT BE USED when strong
|
||||
// security is required as it is not constant time.
|
||||
package uniformdh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const (
|
||||
// Size is the size of a UniformDH key or shared secret in bytes.
|
||||
Size = 1536 / 8
|
||||
|
||||
// modpStr is the RFC3526 1536-bit MODP Group (Group 5).
|
||||
modpStr = "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" +
|
||||
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" +
|
||||
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" +
|
||||
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" +
|
||||
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D" +
|
||||
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F" +
|
||||
"83655D23DCA3AD961C62F356208552BB9ED529077096966D" +
|
||||
"670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF"
|
||||
|
||||
g = 2
|
||||
)
|
||||
|
||||
var modpGroup *big.Int
|
||||
var gen *big.Int
|
||||
|
||||
// A PrivateKey represents a UniformDH private key.
|
||||
type PrivateKey struct {
|
||||
PublicKey
|
||||
privateKey *big.Int
|
||||
}
|
||||
|
||||
// A PublicKey represents a UniformDH public key.
|
||||
type PublicKey struct {
|
||||
bytes []byte
|
||||
publicKey *big.Int
|
||||
}
|
||||
|
||||
// Bytes returns the byte representation of a PublicKey.
|
||||
func (pub *PublicKey) Bytes() (pubBytes []byte, err error) {
|
||||
if len(pub.bytes) != Size || pub.bytes == nil {
|
||||
return nil, fmt.Errorf("public key is not initialized")
|
||||
}
|
||||
pubBytes = make([]byte, Size)
|
||||
copy(pubBytes, pub.bytes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetBytes sets the PublicKey from a byte slice.
|
||||
func (pub *PublicKey) SetBytes(pubBytes []byte) error {
|
||||
if len(pubBytes) != Size {
|
||||
return fmt.Errorf("public key length %d is not %d", len(pubBytes), Size)
|
||||
}
|
||||
pub.bytes = make([]byte, Size)
|
||||
copy(pub.bytes, pubBytes)
|
||||
pub.publicKey = new(big.Int).SetBytes(pub.bytes)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateKey generates a UniformDH keypair using the random source random.
|
||||
func GenerateKey(random io.Reader) (priv *PrivateKey, err error) {
|
||||
privBytes := make([]byte, Size)
|
||||
if _, err = io.ReadFull(random, privBytes); err != nil {
|
||||
return
|
||||
}
|
||||
priv, err = generateKey(privBytes)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func generateKey(privBytes []byte) (priv *PrivateKey, err error) {
|
||||
// This function does all of the actual heavy lifting of creating a public
|
||||
// key from a raw 192 byte private key. It is split so that the KAT tests
|
||||
// can be written easily, and not exposed since non-ephemeral keys are a
|
||||
// terrible idea.
|
||||
|
||||
if len(privBytes) != Size {
|
||||
return nil, fmt.Errorf("invalid private key size %d", len(privBytes))
|
||||
}
|
||||
|
||||
// To pick a private UniformDH key, we pick a random 1536-bit number,
|
||||
// and make it even by setting its low bit to 0
|
||||
privBn := new(big.Int).SetBytes(privBytes)
|
||||
wasEven := privBn.Bit(0) == 0
|
||||
privBn = privBn.SetBit(privBn, 0, 0)
|
||||
|
||||
// Let x be that private key, and X = g^x (mod p).
|
||||
pubBn := new(big.Int).Exp(gen, privBn, modpGroup)
|
||||
pubAlt := new(big.Int).Sub(modpGroup, pubBn)
|
||||
|
||||
// When someone sends her public key to the other party, she randomly
|
||||
// decides whether to send X or p-X. Use the lowest most bit of the
|
||||
// private key here as the random coin flip since it is masked out and not
|
||||
// used.
|
||||
//
|
||||
// Note: The spec doesn't explicitly specify it, but here we prepend zeros
|
||||
// to the key so that it is always exactly Size bytes.
|
||||
pubBytes := make([]byte, Size)
|
||||
if wasEven {
|
||||
err = prependZeroBytes(pubBytes, pubBn.Bytes())
|
||||
} else {
|
||||
err = prependZeroBytes(pubBytes, pubAlt.Bytes())
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
priv = new(PrivateKey)
|
||||
priv.PublicKey.bytes = pubBytes
|
||||
priv.PublicKey.publicKey = pubBn
|
||||
priv.privateKey = privBn
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Handshake generates a shared secret given a PrivateKey and PublicKey.
|
||||
func Handshake(privateKey *PrivateKey, publicKey *PublicKey) (sharedSecret []byte, err error) {
|
||||
// When a party wants to calculate the shared secret, she raises the
|
||||
// foreign public key to her private key.
|
||||
secretBn := new(big.Int).Exp(publicKey.publicKey, privateKey.privateKey, modpGroup)
|
||||
sharedSecret = make([]byte, Size)
|
||||
err = prependZeroBytes(sharedSecret, secretBn.Bytes())
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func prependZeroBytes(dst, src []byte) error {
|
||||
zeros := len(dst) - len(src)
|
||||
if zeros < 0 {
|
||||
return fmt.Errorf("src length is greater than destination: %d", zeros)
|
||||
}
|
||||
for i := 0; i < zeros; i++ {
|
||||
dst[i] = 0
|
||||
}
|
||||
copy(dst[zeros:], src)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Load the MODP group and the generator.
|
||||
var ok bool
|
||||
modpGroup, ok = new(big.Int).SetString(modpStr, 16)
|
||||
if !ok {
|
||||
panic("Failed to load the RFC3526 MODP Group")
|
||||
}
|
||||
gen = big.NewInt(g)
|
||||
}
|
@ -0,0 +1,220 @@
|
||||
/*
|
||||
* 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 uniformdh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
xPrivStr = "6f592d676f536874746f20686e6b776f" +
|
||||
"20736874206561676574202e6f592d67" +
|
||||
"6f536874746f20687369742065686720" +
|
||||
"74612e655920676f532d746f6f686874" +
|
||||
"6920207368742065656b20796e612064" +
|
||||
"7567726169646e616f20206668742065" +
|
||||
"61676574202e61507473202c72707365" +
|
||||
"6e652c746620747572752c6561206c6c" +
|
||||
"612065726f20656e6920206e6f592d67" +
|
||||
"6f536874746f2e68482020656e6b776f" +
|
||||
"2073687772652065687420656c4f2064" +
|
||||
"6e4f736562206f72656b74207268756f"
|
||||
|
||||
xPubStr = "76a3d17d5c55b03e865fa3e8267990a7" +
|
||||
"24baa24b0bdd0cc4af93be8de30be120" +
|
||||
"d5533c91bf63ef923b02edcb84b74438" +
|
||||
"3f7de232cca6eb46d07cad83dcaa317f" +
|
||||
"becbc68ca13e2c4019e6a36531067450" +
|
||||
"04aecc0be1dff0a78733fb0e7d5cb7c4" +
|
||||
"97cab77b1331bf347e5f3a7847aa0bc0" +
|
||||
"f4bc64146b48407fed7b931d16972d25" +
|
||||
"fb4da5e6dc074ce2a58daa8de7624247" +
|
||||
"cdf2ebe4e4dfec6d5989aac778c87559" +
|
||||
"d3213d6040d4111ce3a2acae19f9ee15" +
|
||||
"32509e037f69b252fdc30243cbbce9d0"
|
||||
|
||||
yPrivStr = "736562206f72656b74207268756f6867" +
|
||||
"6f2020666c6f2c646120646e77206568" +
|
||||
"657254206568207968736c61206c7262" +
|
||||
"6165206b68746f726775206867616961" +
|
||||
"2e6e482020656e6b776f207368777265" +
|
||||
"2065685479656820766120657274646f" +
|
||||
"652072616874732766206569646c2c73" +
|
||||
"6120646e772065686572542065682079" +
|
||||
"74736c69206c72746165206468746d65" +
|
||||
"202c6e612064687720796f6e6f20656e" +
|
||||
"63206e61622068656c6f206468546d65" +
|
||||
"61202073685479657420657264610a2e"
|
||||
|
||||
yPubStr = "d04e156e554c37ffd7aba749df662350" +
|
||||
"1e4ff4466cb12be055617c1a36872237" +
|
||||
"36d2c3fdce9ee0f9b27774350849112a" +
|
||||
"a5aeb1f126811c9c2f3a9cb13d2f0c3a" +
|
||||
"7e6fa2d3bf71baf50d839171534f227e" +
|
||||
"fbb2ce4227a38c25abdc5ba7fc430111" +
|
||||
"3a2cb2069c9b305faac4b72bf21fec71" +
|
||||
"578a9c369bcac84e1a7dcf0754e342f5" +
|
||||
"bc8fe4917441b88254435e2abaf297e9" +
|
||||
"3e1e57968672d45bd7d4c8ba1bc3d314" +
|
||||
"889b5bc3d3e4ea33d4f2dfdd34e5e5a7" +
|
||||
"2ff24ee46316d4757dad09366a0b66b3"
|
||||
|
||||
ssStr = "78afaf5f457f1fdb832bebc397644a33" +
|
||||
"038be9dba10ca2ce4a076f327f3a0ce3" +
|
||||
"151d477b869ee7ac467755292ad8a77d" +
|
||||
"b9bd87ffbbc39955bcfb03b1583888c8" +
|
||||
"fd037834ff3f401d463c10f899aa6378" +
|
||||
"445140b7f8386a7d509e7b9db19b677f" +
|
||||
"062a7a1a4e1509604d7a0839ccd5da61" +
|
||||
"73e10afd9eab6dda74539d60493ca37f" +
|
||||
"a5c98cd9640b409cd8bb3be2bc5136fd" +
|
||||
"42e764fc3f3c0ddb8db3d87abcf2e659" +
|
||||
"8d2b101bef7a56f50ebc658f9df1287d" +
|
||||
"a81359543e77e4a4cfa7598a4152e4c0"
|
||||
)
|
||||
|
||||
var xPriv, xPub, yPriv, yPub, ss []byte
|
||||
|
||||
// TestGenerateKeyOdd tests creating a UniformDH keypair with a odd private
|
||||
// key.
|
||||
func TestGenerateKeyOdd(t *testing.T) {
|
||||
xX, err := generateKey(xPriv)
|
||||
if err != nil {
|
||||
t.Fatal("generateKey(xPriv) failed:", err)
|
||||
}
|
||||
|
||||
xPubGen, err := xX.PublicKey.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal("xX.PublicKey.Bytes() failed:", err)
|
||||
}
|
||||
if 0 != bytes.Compare(xPubGen, xPub) {
|
||||
t.Fatal("Generated public key does not match known answer")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateKeyEven tests creating a UniformDH keypair with a even private
|
||||
// key.
|
||||
func TestGenerateKeyEven(t *testing.T) {
|
||||
yY, err := generateKey(yPriv)
|
||||
if err != nil {
|
||||
t.Fatal("generateKey(yPriv) failed:", err)
|
||||
}
|
||||
|
||||
yPubGen, err := yY.PublicKey.Bytes()
|
||||
if err != nil {
|
||||
t.Fatal("yY.PublicKey.Bytes() failed:", err)
|
||||
}
|
||||
if 0 != bytes.Compare(yPubGen, yPub) {
|
||||
t.Fatal("Generated public key does not match known answer")
|
||||
}
|
||||
}
|
||||
|
||||
// TestHandshake tests conductiong a UniformDH handshake with know values.
|
||||
func TestHandshake(t *testing.T) {
|
||||
xX, err := generateKey(xPriv)
|
||||
if err != nil {
|
||||
t.Fatal("generateKey(xPriv) failed:", err)
|
||||
}
|
||||
yY, err := generateKey(yPriv)
|
||||
if err != nil {
|
||||
t.Fatal("generateKey(yPriv) failed:", err)
|
||||
}
|
||||
|
||||
xY, err := Handshake(xX, &yY.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal("Handshake(xX, yY.PublicKey) failed:", err)
|
||||
}
|
||||
yX, err := Handshake(yY, &xX.PublicKey)
|
||||
if err != nil {
|
||||
t.Fatal("Handshake(yY, xX.PublicKey) failed:", err)
|
||||
}
|
||||
|
||||
if 0 != bytes.Compare(xY, yX) {
|
||||
t.Fatal("Generated shared secrets do not match between peers")
|
||||
}
|
||||
if 0 != bytes.Compare(xY, ss) {
|
||||
t.Fatal("Generated shared secret does not match known value")
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark UniformDH key generation + exchange. THe actual time taken per
|
||||
// peer is half of the reported time as this does 2 key generation an
|
||||
// handshake operations.
|
||||
func BenchmarkHandshake(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
xX, err := GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal("Failed to generate xX keypair", err)
|
||||
}
|
||||
|
||||
yY, err := GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
b.Fatal("Failed to generate yY keypair", err)
|
||||
}
|
||||
|
||||
xY, err := Handshake(xX, &yY.PublicKey)
|
||||
if err != nil {
|
||||
b.Fatal("Handshake(xX, yY.PublicKey) failed:", err)
|
||||
}
|
||||
yX, err := Handshake(yY, &xX.PublicKey)
|
||||
if err != nil {
|
||||
b.Fatal("Handshake(yY, xX.PublicKey) failed:", err)
|
||||
}
|
||||
|
||||
_ = xY
|
||||
_ = yX
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Load the test vectors into byte slices.
|
||||
var err error
|
||||
xPriv, err = hex.DecodeString(xPrivStr)
|
||||
if err != nil {
|
||||
panic("hex.DecodeString(xPrivStr) failed")
|
||||
}
|
||||
xPub, err = hex.DecodeString(xPubStr)
|
||||
if err != nil {
|
||||
panic("hex.DecodeString(xPubStr) failed")
|
||||
}
|
||||
yPriv, err = hex.DecodeString(yPrivStr)
|
||||
if err != nil {
|
||||
panic("hex.DecodeString(yPrivStr) failed")
|
||||
}
|
||||
yPub, err = hex.DecodeString(yPubStr)
|
||||
if err != nil {
|
||||
panic("hex.DecodeString(yPubStr) failed")
|
||||
}
|
||||
ss, err = hex.DecodeString(ssStr)
|
||||
if err != nil {
|
||||
panic("hex.DecodeString(ssStr) failed")
|
||||
}
|
||||
}
|
@ -1,758 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Package obfs4 implements the obfs4 protocol. For the most part, obfs4
|
||||
// connections are exposed via the net.Conn and net.Listener interface, though
|
||||
// accepting connections as a server requires calling ServerHandshake on the
|
||||
// conn to finish connection establishment.
|
||||
package obfs4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/drbg"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/framing"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/ntor"
|
||||
)
|
||||
|
||||
const (
|
||||
// SeedLength is the length of the obfs4 polymorphism seed.
|
||||
SeedLength = 32
|
||||
headerLength = framing.FrameOverhead + packetOverhead
|
||||
connectionTimeout = time.Duration(30) * time.Second
|
||||
|
||||
maxCloseDelayBytes = maxHandshakeLength
|
||||
maxCloseDelay = 60
|
||||
|
||||
maxIatDelay = 100
|
||||
)
|
||||
|
||||
type connState int
|
||||
|
||||
const (
|
||||
stateInit connState = iota
|
||||
stateEstablished
|
||||
stateBroken
|
||||
stateClosed
|
||||
)
|
||||
|
||||
// Obfs4Conn is the implementation of the net.Conn interface for obfs4
|
||||
// connections.
|
||||
type Obfs4Conn struct {
|
||||
conn net.Conn
|
||||
|
||||
sessionKey *ntor.Keypair
|
||||
|
||||
lenProbDist *wDist
|
||||
iatProbDist *wDist
|
||||
|
||||
encoder *framing.Encoder
|
||||
decoder *framing.Decoder
|
||||
|
||||
receiveBuffer bytes.Buffer
|
||||
receiveDecodedBuffer bytes.Buffer
|
||||
|
||||
state connState
|
||||
isServer bool
|
||||
|
||||
// Server side state.
|
||||
listener *Obfs4Listener
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func (c *Obfs4Conn) padBurst(burst *bytes.Buffer) (err error) {
|
||||
tailLen := burst.Len() % framing.MaximumSegmentLength
|
||||
toPadTo := c.lenProbDist.sample()
|
||||
|
||||
padLen := 0
|
||||
if toPadTo >= tailLen {
|
||||
padLen = toPadTo - tailLen
|
||||
} else {
|
||||
padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
|
||||
}
|
||||
|
||||
if padLen > headerLength {
|
||||
err = c.producePacket(burst, packetTypePayload, []byte{},
|
||||
uint16(padLen-headerLength))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if padLen > 0 {
|
||||
err = c.producePacket(burst, packetTypePayload, []byte{},
|
||||
maxPacketPayloadLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = c.producePacket(burst, packetTypePayload, []byte{},
|
||||
uint16(padLen))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Obfs4Conn) closeAfterDelay() {
|
||||
// I-it's not like I w-wanna handshake with you or anything. B-b-baka!
|
||||
defer c.conn.Close()
|
||||
|
||||
delay := time.Duration(c.listener.closeDelay)*time.Second + connectionTimeout
|
||||
deadline := c.startTime.Add(delay)
|
||||
if time.Now().After(deadline) {
|
||||
return
|
||||
}
|
||||
|
||||
err := c.conn.SetReadDeadline(deadline)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume and discard data on this connection until either the specified
|
||||
// interval passes or a certain size has been reached.
|
||||
discarded := 0
|
||||
var buf [framing.MaximumSegmentLength]byte
|
||||
for discarded < int(c.listener.closeDelayBytes) {
|
||||
n, err := c.conn.Read(buf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
discarded += n
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Obfs4Conn) setBroken() {
|
||||
c.state = stateBroken
|
||||
}
|
||||
|
||||
func (c *Obfs4Conn) clientHandshake(nodeID *ntor.NodeID, publicKey *ntor.PublicKey) (err error) {
|
||||
if c.isServer {
|
||||
panic(fmt.Sprintf("BUG: clientHandshake() called for server connection"))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
c.sessionKey = nil
|
||||
if err != nil {
|
||||
c.setBroken()
|
||||
}
|
||||
}()
|
||||
|
||||
// Generate/send the client handshake.
|
||||
var hs *clientHandshake
|
||||
var blob []byte
|
||||
hs = newClientHandshake(nodeID, publicKey, c.sessionKey)
|
||||
blob, err = hs.generateHandshake()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = c.conn.SetDeadline(time.Now().Add(connectionTimeout * 2))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = c.conn.Write(blob)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the server handshake.
|
||||
var hsBuf [maxHandshakeLength]byte
|
||||
for {
|
||||
var n int
|
||||
n, err = c.conn.Read(hsBuf[:])
|
||||
if err != nil {
|
||||
// Yes, just bail out of handshaking even if the Read could have
|
||||
// returned data, no point in continuing on EOF/etc.
|
||||
return
|
||||
}
|
||||
c.receiveBuffer.Write(hsBuf[:n])
|
||||
|
||||
var seed []byte
|
||||
n, seed, err = hs.parseServerHandshake(c.receiveBuffer.Bytes())
|
||||
if err == ErrMarkNotFoundYet {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
_ = c.receiveBuffer.Next(n)
|
||||
|
||||
err = c.conn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 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:])
|
||||
|
||||
c.state = stateEstablished
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Obfs4Conn) serverHandshake(nodeID *ntor.NodeID, keypair *ntor.Keypair) (err error) {
|
||||
if !c.isServer {
|
||||
panic(fmt.Sprintf("BUG: serverHandshake() called for client connection"))
|
||||
}
|
||||
|
||||
defer func() {
|
||||
c.sessionKey = nil
|
||||
if err != nil {
|
||||
c.setBroken()
|
||||
}
|
||||
}()
|
||||
|
||||
hs := newServerHandshake(nodeID, keypair, c.sessionKey)
|
||||
err = c.conn.SetDeadline(time.Now().Add(connectionTimeout))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the client handshake.
|
||||
var hsBuf [maxHandshakeLength]byte
|
||||
for {
|
||||
var n int
|
||||
n, err = c.conn.Read(hsBuf[:])
|
||||
if err != nil {
|
||||
// Yes, just bail out of handshaking even if the Read could have
|
||||
// returned data, no point in continuing on EOF/etc.
|
||||
return
|
||||
}
|
||||
c.receiveBuffer.Write(hsBuf[:n])
|
||||
|
||||
var seed []byte
|
||||
seed, err = hs.parseClientHandshake(c.listener.filter, c.receiveBuffer.Bytes())
|
||||
if err == ErrMarkNotFoundYet {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
c.receiveBuffer.Reset()
|
||||
|
||||
err = c.conn.SetDeadline(time.Time{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 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])
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
//
|
||||
// Since the current and only implementation always sends a PRNG seed for
|
||||
// the length obfuscation, this makes the amount of data received from the
|
||||
// server inconsistent with the length sent from the client.
|
||||
//
|
||||
// Rebalance this by tweaking the client mimimum padding/server maximum
|
||||
// padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed
|
||||
// as part of the server response). See inlineSeedFrameLength in
|
||||
// handshake_ntor.go.
|
||||
//
|
||||
|
||||
// Generate/send the response.
|
||||
var blob []byte
|
||||
blob, err = hs.generateHandshake()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var frameBuf bytes.Buffer
|
||||
_, err = frameBuf.Write(blob)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.state = stateEstablished
|
||||
|
||||
// Send the PRNG seed as the first packet.
|
||||
err = c.producePacket(&frameBuf, packetTypePrngSeed, c.listener.rawSeed, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = c.conn.Write(frameBuf.Bytes())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// CanHandshake queries the connection state to see if it is appropriate to
|
||||
// call ServerHandshake to complete connection establishment.
|
||||
func (c *Obfs4Conn) CanHandshake() bool {
|
||||
return c.state == stateInit
|
||||
}
|
||||
|
||||
// CanReadWrite queries the connection state to see if it is possible to read
|
||||
// and write data.
|
||||
func (c *Obfs4Conn) CanReadWrite() bool {
|
||||
return c.state == stateEstablished
|
||||
}
|
||||
|
||||
// ServerHandshake completes the server side of the obfs4 handshake. Servers
|
||||
// are required to call this after accepting a connection. ServerHandshake
|
||||
// will treat errors encountered during the handshake as fatal and drop the
|
||||
// connection before returning.
|
||||
func (c *Obfs4Conn) ServerHandshake() error {
|
||||
// Handshakes when already established are a no-op.
|
||||
if c.CanReadWrite() {
|
||||
return nil
|
||||
} else if !c.CanHandshake() {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
if !c.isServer {
|
||||
panic(fmt.Sprintf("BUG: ServerHandshake() called for client connection"))
|
||||
}
|
||||
|
||||
// Complete the handshake.
|
||||
err := c.serverHandshake(c.listener.nodeID, c.listener.keyPair)
|
||||
if err != nil {
|
||||
c.closeAfterDelay()
|
||||
}
|
||||
c.listener = nil
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Read implements the net.Conn Read method.
|
||||
func (c *Obfs4Conn) Read(b []byte) (n int, err error) {
|
||||
if !c.CanReadWrite() {
|
||||
return 0, syscall.EINVAL
|
||||
}
|
||||
|
||||
for c.receiveDecodedBuffer.Len() == 0 {
|
||||
_, err = c.consumeFramedPackets(nil)
|
||||
if err == framing.ErrAgain {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
n, err = c.receiveDecodedBuffer.Read(b)
|
||||
return
|
||||
}
|
||||
|
||||
// WriteTo implements the io.WriterTo WriteTo method.
|
||||
func (c *Obfs4Conn) WriteTo(w io.Writer) (n int64, err error) {
|
||||
if !c.CanReadWrite() {
|
||||
return 0, syscall.EINVAL
|
||||
}
|
||||
|
||||
// If there is buffered payload from earlier Read() calls, write.
|
||||
wrLen := 0
|
||||
if c.receiveDecodedBuffer.Len() > 0 {
|
||||
wrLen, err = w.Write(c.receiveDecodedBuffer.Bytes())
|
||||
if err != nil {
|
||||
c.setBroken()
|
||||
return int64(wrLen), err
|
||||
} else if wrLen < int(c.receiveDecodedBuffer.Len()) {
|
||||
c.setBroken()
|
||||
return int64(wrLen), io.ErrShortWrite
|
||||
}
|
||||
c.receiveDecodedBuffer.Reset()
|
||||
}
|
||||
|
||||
for {
|
||||
wrLen, err = c.consumeFramedPackets(w)
|
||||
n += int64(wrLen)
|
||||
if err == framing.ErrAgain {
|
||||
continue
|
||||
} else if err != nil {
|
||||
// io.EOF is treated as not an error.
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Write implements the net.Conn Write method. The obfs4 lengt obfuscation is
|
||||
// done based on the amount of data passed to Write (each call to Write results
|
||||
// in up to 2 frames of padding). Passing excessively short buffers to Write
|
||||
// will result in significant overhead.
|
||||
func (c *Obfs4Conn) Write(b []byte) (n int, err error) {
|
||||
if !c.CanReadWrite() {
|
||||
return 0, syscall.EINVAL
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.setBroken()
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO: Change this to write directly to c.conn skipping frameBuf.
|
||||
chopBuf := bytes.NewBuffer(b)
|
||||
var payload [maxPacketPayloadLength]byte
|
||||
var frameBuf bytes.Buffer
|
||||
|
||||
for chopBuf.Len() > 0 {
|
||||
// Send maximum sized frames.
|
||||
rdLen := 0
|
||||
rdLen, err = chopBuf.Read(payload[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if rdLen == 0 {
|
||||
panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
|
||||
}
|
||||
n += rdLen
|
||||
|
||||
err = c.producePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert random padding. In theory for some padding lengths, this can be
|
||||
// inlined with the payload, but doing it this way simplifies the code
|
||||
// significantly.
|
||||
err = c.padBurst(&frameBuf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Spit frame(s) onto the network.
|
||||
//
|
||||
// Partial writes are fatal because the frame encoder state is advanced
|
||||
// at this point. It's possible to keep frameBuf around, but fuck it.
|
||||
// Someone that wants write timeouts can change this.
|
||||
if c.iatProbDist != nil {
|
||||
var iatFrame [framing.MaximumSegmentLength]byte
|
||||
for frameBuf.Len() > 0 {
|
||||
iatWrLen := 0
|
||||
iatWrLen, err = frameBuf.Read(iatFrame[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if iatWrLen == 0 {
|
||||
panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
|
||||
}
|
||||
|
||||
// Calculate the delay. The delay resolution is 100 usec, leading
|
||||
// to a maximum delay of 10 msec.
|
||||
iatDelta := time.Duration(c.iatProbDist.sample() * 100)
|
||||
|
||||
// Write then sleep.
|
||||
_, err = c.conn.Write(iatFrame[:iatWrLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
time.Sleep(iatDelta * time.Microsecond)
|
||||
}
|
||||
} else {
|
||||
_, err = c.conn.Write(frameBuf.Bytes())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the connection.
|
||||
func (c *Obfs4Conn) Close() error {
|
||||
if c.conn == nil {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
c.state = stateClosed
|
||||
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// LocalAddr returns the local network address.
|
||||
func (c *Obfs4Conn) LocalAddr() net.Addr {
|
||||
if c.state == stateClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.conn.LocalAddr()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote network address.
|
||||
func (c *Obfs4Conn) RemoteAddr() net.Addr {
|
||||
if c.state == stateClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.conn.RemoteAddr()
|
||||
}
|
||||
|
||||
// SetDeadline is a convoluted way to get syscall.ENOTSUP.
|
||||
func (c *Obfs4Conn) SetDeadline(t time.Time) error {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
// SetReadDeadline implements the net.Conn SetReadDeadline method. Connections
|
||||
// must be in the established state (CanReadWrite).
|
||||
func (c *Obfs4Conn) SetReadDeadline(t time.Time) error {
|
||||
if !c.CanReadWrite() {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
|
||||
return c.conn.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetWriteDeadline is a convoluted way to get syscall.ENOTSUP.
|
||||
func (c *Obfs4Conn) SetWriteDeadline(t time.Time) error {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
// DialFn is a function pointer to a dial routine that matches the
|
||||
// net.Dialer.Dial routine.
|
||||
type DialFn func(string, string) (net.Conn, error)
|
||||
|
||||
// DialObfs4 connects to the remote address on the network, and handshakes with
|
||||
// the peer's obfs4 Node ID and Identity Public Key. nodeID and publicKey are
|
||||
// expected as strings containing the Base64 encoded values.
|
||||
func DialObfs4(network, address, nodeID, publicKey string, iatObfuscation bool) (*Obfs4Conn, error) {
|
||||
|
||||
return DialObfs4DialFn(net.Dial, network, address, nodeID, publicKey, iatObfuscation)
|
||||
}
|
||||
|
||||
// DialObfs4DialFn connects to the remote address on the network via DialFn,
|
||||
// and handshakes with the peers' obfs4 Node ID and Identity Public Key.
|
||||
func DialObfs4DialFn(dialFn DialFn, network, address, nodeID, publicKey string, iatObfuscation bool) (*Obfs4Conn, 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
|
||||
}
|
||||
|
||||
// Generate the initial length obfuscation distribution.
|
||||
seed, err := drbg.NewSeed()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the Obfs4Conn.
|
||||
c := new(Obfs4Conn)
|
||||
c.lenProbDist = newWDist(seed, 0, framing.MaximumSegmentLength)
|
||||
if iatObfuscation {
|
||||
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
|
||||
iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.iatProbDist = newWDist(iatSeed, 0, maxIatDelay)
|
||||
}
|
||||
|
||||
// Generate the session keypair *before* connecting to the remote peer.
|
||||
c.sessionKey, err = ntor.NewKeypair(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Connect to the remote peer.
|
||||
c.conn, err = dialFn(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 is the implementation of the net.Listener interface for obfs4
|
||||
// connections.
|
||||
type Obfs4Listener struct {
|
||||
listener net.Listener
|
||||
|
||||
filter *replayFilter
|
||||
|
||||
keyPair *ntor.Keypair
|
||||
nodeID *ntor.NodeID
|
||||
|
||||
rawSeed []byte
|
||||
seed *drbg.Seed
|
||||
iatSeed *drbg.Seed
|
||||
iatObfuscation bool
|
||||
|
||||
closeDelayBytes int
|
||||
closeDelay int
|
||||
}
|
||||
|
||||
// Accept implements the Accept method of the net.Listener interface; it waits
|
||||
// for the next call and returns a generic net.Conn. Callers are responsible
|
||||
// for completing the handshake by calling Obfs4Conn.ServerHandshake().
|
||||
func (l *Obfs4Listener) Accept() (net.Conn, error) {
|
||||
conn, err := l.AcceptObfs4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// AcceptObfs4 accepts the next incoming call and returns a new connection.
|
||||
// Callers are responsible for completing the handshake by calling
|
||||
// Obfs4Conn.ServerHandshake().
|
||||
func (l *Obfs4Listener) AcceptObfs4() (*Obfs4Conn, error) {
|
||||
// Accept a connection.
|
||||
c, err := l.listener.Accept()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Allocate the obfs4 connection state.
|
||||
cObfs := new(Obfs4Conn)
|
||||
|
||||
// Generate the session keypair *before* consuming data from the peer, to
|
||||
// add more noise to the keypair generation time. The idea is that jitter
|
||||
// here is masked by network latency (the time it takes for a server to
|
||||
// accept a socket out of the backlog should not be fixed, and the client
|
||||
// needs to send the public key).
|
||||
cObfs.sessionKey, err = ntor.NewKeypair(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cObfs.conn = c
|
||||
cObfs.isServer = true
|
||||
cObfs.listener = l
|
||||
cObfs.lenProbDist = newWDist(l.seed, 0, framing.MaximumSegmentLength)
|
||||
if l.iatObfuscation {
|
||||
cObfs.iatProbDist = newWDist(l.iatSeed, 0, maxIatDelay)
|
||||
}
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
cObfs.startTime = time.Now()
|
||||
|
||||
return cObfs, nil
|
||||
}
|
||||
|
||||
// Close stops listening on the Obfs4 endpoint. Already Accepted connections
|
||||
// are not closed.
|
||||
func (l *Obfs4Listener) Close() error {
|
||||
return l.listener.Close()
|
||||
}
|
||||
|
||||
// Addr returns the listener's network address.
|
||||
func (l *Obfs4Listener) Addr() net.Addr {
|
||||
return l.listener.Addr()
|
||||
}
|
||||
|
||||
// PublicKey returns the listener's Identity Public Key, a Base64 encoded
|
||||
// obfs4.ntor.PublicKey.
|
||||
func (l *Obfs4Listener) PublicKey() string {
|
||||
if l.keyPair == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return l.keyPair.Public().Base64()
|
||||
}
|
||||
|
||||
// NodeID returns the listener's NodeID, a Base64 encoded obfs4.ntor.NodeID.
|
||||
func (l *Obfs4Listener) NodeID() string {
|
||||
if l.nodeID == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return l.nodeID.Base64()
|
||||
}
|
||||
|
||||
// ListenObfs4 annnounces on the network and address, and returns and
|
||||
// Obfs4Listener. nodeId, privateKey and seed are expected as strings
|
||||
// containing the Base64 encoded values.
|
||||
func ListenObfs4(network, laddr, nodeID, privateKey, seed string, iatObfuscation bool) (*Obfs4Listener, 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
|
||||
}
|
||||
l.rawSeed, err = base64.StdEncoding.DecodeString(seed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.seed, err = drbg.SeedFromBytes(l.rawSeed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.iatObfuscation = iatObfuscation
|
||||
if l.iatObfuscation {
|
||||
iatSeedSrc := sha256.Sum256(l.seed.Bytes()[:])
|
||||
l.iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
l.filter, err = newReplayFilter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rng := rand.New(drbg.NewHashDrbg(l.seed))
|
||||
l.closeDelayBytes = rng.Intn(maxCloseDelayBytes)
|
||||
l.closeDelay = rng.Intn(maxCloseDelay)
|
||||
|
||||
// Start up the listener.
|
||||
l.listener, err = net.Listen(network, laddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
/* vim :set ts=4 sw=4 sts=4 noet : */
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"code.google.com/p/go.net/proxy"
|
||||
|
||||
"git.torproject.org/pluggable-transports/obfs4.git"
|
||||
)
|
||||
|
||||
// getProxyDialer is a trival wrapper around the go.net/proxy package to avoid
|
||||
// having it as a dependency for anything else.
|
||||
func getProxyDialer(uri *url.URL) (obfs4.DialFn, error) {
|
||||
if uri == nil {
|
||||
return proxy.Direct.Dial, nil
|
||||
}
|
||||
|
||||
dialer, err := proxy.FromURL(uri, proxy.Direct)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dialer.Dial, nil
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package obfs4
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
|
||||
"github.com/dchest/siphash"
|
||||
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/csrand"
|
||||
)
|
||||
|
||||
// maxFilterSize is the maximum capacity of the replay filter. The busiest
|
||||
// bridge I know about processes something along the order of 3000 connections
|
||||
// per day. The maximum timespan any entry can live in the filter is 2 hours,
|
||||
// so this value should be sufficient.
|
||||
const maxFilterSize = 100 * 1024
|
||||
|
||||
// replayFilter is a simple filter designed only to answer if it has seen a
|
||||
// given byte sequence before. It is based around comparing the SipHash-2-4
|
||||
// digest of data to match against. Collisions are treated as positive matches
|
||||
// however, the probability of such occurences is negligible.
|
||||
type replayFilter struct {
|
||||
lock sync.Mutex
|
||||
key [2]uint64
|
||||
filter map[uint64]*filterEntry
|
||||
fifo *list.List
|
||||
}
|
||||
|
||||
type filterEntry struct {
|
||||
firstSeen int64
|
||||
hash uint64
|
||||
element *list.Element
|
||||
}
|
||||
|
||||
// newReplayFilter creates a new replayFilter instance.
|
||||
func newReplayFilter() (filter *replayFilter, err error) {
|
||||
// Initialize the SipHash-2-4 instance with a random key.
|
||||
var key [16]byte
|
||||
err = csrand.Bytes(key[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
filter = new(replayFilter)
|
||||
filter.key[0] = binary.BigEndian.Uint64(key[0:8])
|
||||
filter.key[1] = binary.BigEndian.Uint64(key[8:16])
|
||||
filter.filter = make(map[uint64]*filterEntry)
|
||||
filter.fifo = list.New()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// testAndSet queries the filter for buf, adds it if it was not present and
|
||||
// returns if it has added the entry or not. This method is threadsafe.
|
||||
func (f *replayFilter) testAndSet(now int64, buf []byte) bool {
|
||||
hash := siphash.Hash(f.key[0], f.key[1], buf)
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.compactFilter(now)
|
||||
|
||||
entry := f.filter[hash]
|
||||
if entry != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
entry = new(filterEntry)
|
||||
entry.hash = hash
|
||||
entry.firstSeen = now
|
||||
entry.element = f.fifo.PushBack(entry)
|
||||
f.filter[hash] = entry
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// compactFilter purges entries that are too old to be relevant. If the filter
|
||||
// is filled to maxFilterCapacity, it will force purge a single entry. This
|
||||
// method is NOT threadsafe.
|
||||
func (f *replayFilter) compactFilter(now int64) {
|
||||
e := f.fifo.Front()
|
||||
for e != nil {
|
||||
entry, _ := e.Value.(*filterEntry)
|
||||
|
||||
// If the filter is at max capacity, force purge at least one entry.
|
||||
if f.fifo.Len() < maxFilterSize {
|
||||
deltaT := now - entry.firstSeen
|
||||
if deltaT < 0 {
|
||||
// Aeeeeeee, the system time jumped backwards, potentially by
|
||||
// a lot. This will eventually self-correct, but "eventually"
|
||||
// could be a long time. As much as this sucks, jettison the
|
||||
// entire filter.
|
||||
f.reset()
|
||||
return
|
||||
}
|
||||
if deltaT < 3600*2 {
|
||||
// Why yes, this is 2 hours. The MAC code includes a hour
|
||||
// resolution timestamp, but to deal with clock skew, it
|
||||
// accepts time +- 1 hour.
|
||||
break
|
||||
}
|
||||
}
|
||||
eNext := e.Next()
|
||||
delete(f.filter, entry.hash)
|
||||
f.fifo.Remove(entry.element)
|
||||
entry.element = nil
|
||||
e = eNext
|
||||
}
|
||||
}
|
||||
|
||||
// reset purges the entire filter. This methoid is NOT threadsafe.
|
||||
func (f *replayFilter) reset() {
|
||||
f.filter = make(map[uint64]*filterEntry)
|
||||
f.fifo = list.New()
|
||||
}
|
||||
|
||||
/* vim :set ts=4 sw=4 sts=4 noet : */
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 base provides the common interface that each supported transport
|
||||
// protocol must implement.
|
||||
package base
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"git.torproject.org/pluggable-transports/goptlib.git"
|
||||
)
|
||||
|
||||
// ClientFactory is the interface that defines the factory for creating
|
||||
// pluggable transport protocol client instances.
|
||||
type ClientFactory interface {
|
||||
// Transport returns the Transport instance that this ClientFactory belongs
|
||||
// to.
|
||||
Transport() Transport
|
||||
|
||||
// ParseArgs parses the supplied arguments into an internal representation
|
||||
// for use with WrapConn. This routine is called before the outgoing
|
||||
// TCP/IP connection is created to allow doing things (like keypair
|
||||
// generation) to be hidden from third parties.
|
||||
ParseArgs(args *pt.Args) (interface{}, error)
|
||||
|
||||
// WrapConn wraps the provided net.Conn with a transport protocol
|
||||
// implementation, and does whatever is required (eg: handshaking) to get
|
||||
// the connection to a point where it is ready to relay data.
|
||||
WrapConn(conn net.Conn, args interface{}) (net.Conn, error)
|
||||
}
|
||||
|
||||
// ServerFactory is the interface that defines the factory for creating
|
||||
// plugable transport protocol server instances. As the arguments are the
|
||||
// property of the factory, validation is done at factory creation time.
|
||||
type ServerFactory interface {
|
||||
// Transport returns the Transport instance that this ServerFactory belongs
|
||||
// to.
|
||||
Transport() Transport
|
||||
|
||||
// Args returns the Args required on the client side to handshake with
|
||||
// server connections created by this factory.
|
||||
Args() *pt.Args
|
||||
|
||||
// WrapConn wraps the provided net.Conn with a transport protocol
|
||||
// implementation, and does whatever is required (eg: handshaking) to get
|
||||
// the connection to a point where it is ready to relay data.
|
||||
WrapConn(conn net.Conn) (net.Conn, error)
|
||||
}
|
||||
|
||||
// Transport is an interface that defines a pluggable transport protocol.
|
||||
type Transport interface {
|
||||
// Name returns the name of the transport protocol. It MUST be a valid C
|
||||
// identifier.
|
||||
Name() string
|
||||
|
||||
// ClientFactory returns a ClientFactory instance for this transport
|
||||
// protocol.
|
||||
ClientFactory(stateDir string) (ClientFactory, error)
|
||||
|
||||
// ServerFactory returns a ServerFactory instance for this transport
|
||||
// protocol. This can fail if the provided arguments are invalid.
|
||||
ServerFactory(stateDir string, args *pt.Args) (ServerFactory, error)
|
||||
}
|
@ -0,0 +1,367 @@
|
||||
/*
|
||||
* 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 obfs2 provides an implementation of the Tor Project's obfs2
|
||||
// obfuscation protocol. This protocol is considered trivially broken by most
|
||||
// sophisticated adversaries.
|
||||
package obfs2
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"git.torproject.org/pluggable-transports/goptlib.git"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
|
||||
)
|
||||
|
||||
const (
|
||||
transportName = "obfs2"
|
||||
sharedSecretArg = "shared-secret"
|
||||
|
||||
clientHandshakeTimeout = time.Duration(30) * time.Second
|
||||
serverHandshakeTimeout = time.Duration(30) * time.Second
|
||||
|
||||
magicValue = 0x2bf5ca7e
|
||||
initiatorPadString = "Initiator obfuscation padding"
|
||||
responderPadString = "Responder obfuscation padding"
|
||||
initiatorKdfString = "Initiator obfuscated data"
|
||||
responderKdfString = "Responder obfuscated data"
|
||||
maxPadding = 8192
|
||||
keyLen = 16
|
||||
seedLen = 16
|
||||
hsLen = 4 + 4
|
||||
)
|
||||
|
||||
func validateArgs(args *pt.Args) error {
|
||||
if _, ok := args.Get(sharedSecretArg); ok {
|
||||
// "shared-secret" is something no bridges use in practice and is thus
|
||||
// unimplemented.
|
||||
return fmt.Errorf("unsupported argument '%s'", sharedSecretArg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transport is the obfs2 implementation of the base.Transport interface.
|
||||
type Transport struct{}
|
||||
|
||||
// Name returns the name of the obfs2 transport protocol.
|
||||
func (t *Transport) Name() string {
|
||||
return transportName
|
||||
}
|
||||
|
||||
// ClientFactory returns a new obfs2ClientFactory instance.
|
||||
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
|
||||
cf := &obfs2ClientFactory{transport: t}
|
||||
return cf, nil
|
||||
}
|
||||
|
||||
// ServerFactory returns a new obfs2ServerFactory instance.
|
||||
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
|
||||
if err := validateArgs(args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sf := &obfs2ServerFactory{t}
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
type obfs2ClientFactory struct {
|
||||
transport base.Transport
|
||||
}
|
||||
|
||||
func (cf *obfs2ClientFactory) Transport() base.Transport {
|
||||
return cf.transport
|
||||
}
|
||||
|
||||
func (cf *obfs2ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
|
||||
return nil, validateArgs(args)
|
||||
}
|
||||
|
||||
func (cf *obfs2ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
|
||||
return newObfs2ClientConn(conn)
|
||||
}
|
||||
|
||||
type obfs2ServerFactory struct {
|
||||
transport base.Transport
|
||||
}
|
||||
|
||||
func (sf *obfs2ServerFactory) Transport() base.Transport {
|
||||
return sf.transport
|
||||
}
|
||||
|
||||
func (sf *obfs2ServerFactory) Args() *pt.Args {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sf *obfs2ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
|
||||
return newObfs2ServerConn(conn)
|
||||
}
|
||||
|
||||
type obfs2Conn struct {
|
||||
net.Conn
|
||||
|
||||
isInitiator bool
|
||||
|
||||
rx *cipher.StreamReader
|
||||
tx *cipher.StreamWriter
|
||||
}
|
||||
|
||||
func (conn *obfs2Conn) Read(b []byte) (int, error) {
|
||||
return conn.rx.Read(b)
|
||||
}
|
||||
|
||||
func (conn *obfs2Conn) Write(b []byte) (int, error) {
|
||||
return conn.tx.Write(b)
|
||||
}
|
||||
|
||||
func newObfs2ClientConn(conn net.Conn) (c *obfs2Conn, err error) {
|
||||
// Initialize a client connection, and start the handshake timeout.
|
||||
c = &obfs2Conn{conn, true, nil, nil}
|
||||
deadline := time.Now().Add(clientHandshakeTimeout)
|
||||
if err = c.SetDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handshake.
|
||||
if err = c.handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Disarm the handshake timer.
|
||||
if err = c.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newObfs2ServerConn(conn net.Conn) (c *obfs2Conn, err error) {
|
||||
// Initialize a server connection, and start the handshake timeout.
|
||||
c = &obfs2Conn{conn, false, nil, nil}
|
||||
deadline := time.Now().Add(serverHandshakeTimeout)
|
||||
if err = c.SetDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handshake.
|
||||
if err = c.handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Disarm the handshake timer.
|
||||
if err = c.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs2Conn) handshake() (err error) {
|
||||
// Each begins by generating a seed and a padding key as follows.
|
||||
// The initiator generates:
|
||||
//
|
||||
// INIT_SEED = SR(SEED_LENGTH)
|
||||
// INIT_PAD_KEY = MAC("Initiator obfuscation padding", INIT_SEED)[:KEYLEN]
|
||||
//
|
||||
// And the responder generates:
|
||||
//
|
||||
// RESP_SEED = SR(SEED_LENGTH)
|
||||
// RESP_PAD_KEY = MAC("Responder obfuscation padding", INIT_SEED)[:KEYLEN]
|
||||
//
|
||||
// Each then generates a random number PADLEN in range from 0 through
|
||||
// MAX_PADDING (inclusive).
|
||||
var seed [seedLen]byte
|
||||
if err = csrand.Bytes(seed[:]); err != nil {
|
||||
return
|
||||
}
|
||||
var padMagic []byte
|
||||
if conn.isInitiator {
|
||||
padMagic = []byte(initiatorPadString)
|
||||
} else {
|
||||
padMagic = []byte(responderPadString)
|
||||
}
|
||||
padKey, padIV := hsKdf(padMagic, seed[:], conn.isInitiator)
|
||||
padLen := uint32(csrand.IntRange(0, maxPadding))
|
||||
|
||||
hsBlob := make([]byte, hsLen+padLen)
|
||||
binary.BigEndian.PutUint32(hsBlob[0:4], magicValue)
|
||||
binary.BigEndian.PutUint32(hsBlob[4:8], padLen)
|
||||
if padLen > 0 {
|
||||
if err = csrand.Bytes(hsBlob[8:]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// The initiator then sends:
|
||||
//
|
||||
// INIT_SEED | E(INIT_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN))
|
||||
//
|
||||
// and the responder sends:
|
||||
//
|
||||
// RESP_SEED | E(RESP_PAD_KEY, UINT32(MAGIC_VALUE) | UINT32(PADLEN) | WR(PADLEN))
|
||||
var txBlock cipher.Block
|
||||
if txBlock, err = aes.NewCipher(padKey); err != nil {
|
||||
return
|
||||
}
|
||||
txStream := cipher.NewCTR(txBlock, padIV)
|
||||
conn.tx = &cipher.StreamWriter{txStream, conn.Conn, nil}
|
||||
if _, err = conn.Conn.Write(seed[:]); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = conn.Write(hsBlob); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Upon receiving the SEED from the other party, each party derives
|
||||
// the other party's padding key value as above, and decrypts the next
|
||||
// 8 bytes of the key establishment message.
|
||||
var peerSeed [seedLen]byte
|
||||
if _, err = io.ReadFull(conn.Conn, peerSeed[:]); err != nil {
|
||||
return
|
||||
}
|
||||
var peerPadMagic []byte
|
||||
if conn.isInitiator {
|
||||
peerPadMagic = []byte(responderPadString)
|
||||
} else {
|
||||
peerPadMagic = []byte(initiatorPadString)
|
||||
}
|
||||
peerKey, peerIV := hsKdf(peerPadMagic, peerSeed[:], !conn.isInitiator)
|
||||
var rxBlock cipher.Block
|
||||
if rxBlock, err = aes.NewCipher(peerKey); err != nil {
|
||||
return
|
||||
}
|
||||
rxStream := cipher.NewCTR(rxBlock, peerIV)
|
||||
conn.rx = &cipher.StreamReader{rxStream, conn.Conn}
|
||||
hsHdr := make([]byte, hsLen)
|
||||
if _, err = io.ReadFull(conn, hsHdr[:]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// If the MAGIC_VALUE does not match, or the PADLEN value is greater than
|
||||
// MAX_PADDING, the party receiving it should close the connection
|
||||
// immediately.
|
||||
if peerMagic := binary.BigEndian.Uint32(hsHdr[0:4]); peerMagic != magicValue {
|
||||
err = fmt.Errorf("invalid magic value: %x", peerMagic)
|
||||
return
|
||||
}
|
||||
padLen = binary.BigEndian.Uint32(hsHdr[4:8])
|
||||
if padLen > maxPadding {
|
||||
err = fmt.Errorf("padlen too long: %d", padLen)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, it should read the remaining PADLEN bytes of padding data
|
||||
// and discard them.
|
||||
tmp := make([]byte, padLen)
|
||||
if _, err = io.ReadFull(conn.Conn, tmp); err != nil { // Note: Skips AES.
|
||||
return
|
||||
}
|
||||
|
||||
// Derive the actual keys.
|
||||
if err = conn.kdf(seed[:], peerSeed[:]); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs2Conn) kdf(seed, peerSeed []byte) (err error) {
|
||||
// Additional keys are then derived as:
|
||||
//
|
||||
// INIT_SECRET = MAC("Initiator obfuscated data", INIT_SEED|RESP_SEED)
|
||||
// RESP_SECRET = MAC("Responder obfuscated data", INIT_SEED|RESP_SEED)
|
||||
// INIT_KEY = INIT_SECRET[:KEYLEN]
|
||||
// INIT_IV = INIT_SECRET[KEYLEN:]
|
||||
// RESP_KEY = RESP_SECRET[:KEYLEN]
|
||||
// RESP_IV = RESP_SECRET[KEYLEN:]
|
||||
combSeed := make([]byte, 0, seedLen*2)
|
||||
if conn.isInitiator {
|
||||
combSeed = append(combSeed, seed...)
|
||||
combSeed = append(combSeed, peerSeed...)
|
||||
} else {
|
||||
combSeed = append(combSeed, peerSeed...)
|
||||
combSeed = append(combSeed, seed...)
|
||||
}
|
||||
|
||||
initKey, initIV := hsKdf([]byte(initiatorKdfString), combSeed, true)
|
||||
var initBlock cipher.Block
|
||||
if initBlock, err = aes.NewCipher(initKey); err != nil {
|
||||
return
|
||||
}
|
||||
initStream := cipher.NewCTR(initBlock, initIV)
|
||||
|
||||
respKey, respIV := hsKdf([]byte(responderKdfString), combSeed, false)
|
||||
var respBlock cipher.Block
|
||||
if respBlock, err = aes.NewCipher(respKey); err != nil {
|
||||
return
|
||||
}
|
||||
respStream := cipher.NewCTR(respBlock, respIV)
|
||||
|
||||
if conn.isInitiator {
|
||||
conn.tx.S = initStream
|
||||
conn.rx.S = respStream
|
||||
} else {
|
||||
conn.tx.S = respStream
|
||||
conn.rx.S = initStream
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func hsKdf(magic, seed []byte, isInitiator bool) (padKey, padIV []byte) {
|
||||
// The actual key/IV is derived in the form of:
|
||||
// m = MAC(magic, seed)
|
||||
// KEY = m[:KEYLEN]
|
||||
// IV = m[KEYLEN:]
|
||||
m := mac(magic, seed)
|
||||
padKey = m[:keyLen]
|
||||
padIV = m[keyLen:]
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func mac(s, x []byte) []byte {
|
||||
// H(x) is SHA256 of x.
|
||||
// MAC(s, x) = H(s | x | s)
|
||||
h := sha256.New()
|
||||
h.Write(s)
|
||||
h.Write(x)
|
||||
h.Write(s)
|
||||
return h.Sum(nil)
|
||||
}
|
||||
|
||||
var _ base.ClientFactory = (*obfs2ClientFactory)(nil)
|
||||
var _ base.ServerFactory = (*obfs2ServerFactory)(nil)
|
||||
var _ base.Transport = (*Transport)(nil)
|
||||
var _ net.Conn = (*obfs2Conn)(nil)
|
@ -0,0 +1,358 @@
|
||||
/*
|
||||
* 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 obfs3 provides an implementation of the Tor Project's obfs3
|
||||
// obfuscation protocol.
|
||||
package obfs3
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"git.torproject.org/pluggable-transports/goptlib.git"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/uniformdh"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
|
||||
)
|
||||
|
||||
const (
|
||||
transportName = "obfs3"
|
||||
|
||||
clientHandshakeTimeout = time.Duration(30) * time.Second
|
||||
serverHandshakeTimeout = time.Duration(30) * time.Second
|
||||
|
||||
initiatorKdfString = "Initiator obfuscated data"
|
||||
responderKdfString = "Responder obfuscated data"
|
||||
initiatorMagicString = "Initiator magic"
|
||||
responderMagicString = "Responder magic"
|
||||
maxPadding = 8194
|
||||
keyLen = 16
|
||||
)
|
||||
|
||||
// Transport is the obfs3 implementation of the base.Transport interface.
|
||||
type Transport struct{}
|
||||
|
||||
// Name returns the name of the obfs3 transport protocol.
|
||||
func (t *Transport) Name() string {
|
||||
return transportName
|
||||
}
|
||||
|
||||
// ClientFactory returns a new obfs3ClientFactory instance.
|
||||
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
|
||||
cf := &obfs3ClientFactory{transport: t}
|
||||
return cf, nil
|
||||
}
|
||||
|
||||
// ServerFactory returns a new obfs3ServerFactory instance.
|
||||
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
|
||||
sf := &obfs3ServerFactory{transport: t}
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
type obfs3ClientFactory struct {
|
||||
transport base.Transport
|
||||
}
|
||||
|
||||
func (cf *obfs3ClientFactory) Transport() base.Transport {
|
||||
return cf.transport
|
||||
}
|
||||
|
||||
func (cf *obfs3ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (cf *obfs3ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
|
||||
return newObfs3ClientConn(conn)
|
||||
}
|
||||
|
||||
type obfs3ServerFactory struct {
|
||||
transport base.Transport
|
||||
}
|
||||
|
||||
func (sf *obfs3ServerFactory) Transport() base.Transport {
|
||||
return sf.transport
|
||||
}
|
||||
|
||||
func (sf *obfs3ServerFactory) Args() *pt.Args {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sf *obfs3ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
|
||||
return newObfs3ServerConn(conn)
|
||||
}
|
||||
|
||||
type obfs3Conn struct {
|
||||
net.Conn
|
||||
|
||||
isInitiator bool
|
||||
rxMagic []byte
|
||||
txMagic []byte
|
||||
rxBuf *bytes.Buffer
|
||||
|
||||
rx *cipher.StreamReader
|
||||
tx *cipher.StreamWriter
|
||||
}
|
||||
|
||||
func newObfs3ClientConn(conn net.Conn) (c *obfs3Conn, err error) {
|
||||
// Initialize a client connection, and start the handshake timeout.
|
||||
c = &obfs3Conn{conn, true, nil, nil, new(bytes.Buffer), nil, nil}
|
||||
deadline := time.Now().Add(clientHandshakeTimeout)
|
||||
if err = c.SetDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handshake.
|
||||
if err = c.handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Disarm the handshake timer.
|
||||
if err = c.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func newObfs3ServerConn(conn net.Conn) (c *obfs3Conn, err error) {
|
||||
// Initialize a server connection, and start the handshake timeout.
|
||||
c = &obfs3Conn{conn, false, nil, nil, new(bytes.Buffer), nil, nil}
|
||||
deadline := time.Now().Add(serverHandshakeTimeout)
|
||||
if err = c.SetDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handshake.
|
||||
if err = c.handshake(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Disarm the handshake timer.
|
||||
if err = c.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs3Conn) handshake() (err error) {
|
||||
// The party who opens the connection is the 'initiator'; the one who
|
||||
// accepts it is the 'responder'. Each begins by generating a
|
||||
// UniformDH keypair, and a random number PADLEN in [0, MAX_PADDING/2].
|
||||
// Both parties then send:
|
||||
//
|
||||
// PUB_KEY | WR(PADLEN)
|
||||
var privateKey *uniformdh.PrivateKey
|
||||
if privateKey, err = uniformdh.GenerateKey(csrand.Reader); err != nil {
|
||||
return
|
||||
}
|
||||
padLen := csrand.IntRange(0, maxPadding/2)
|
||||
blob := make([]byte, uniformdh.Size+padLen)
|
||||
var publicKey []byte
|
||||
if publicKey, err = privateKey.PublicKey.Bytes(); err != nil {
|
||||
return
|
||||
}
|
||||
copy(blob[0:], publicKey)
|
||||
if err = csrand.Bytes(blob[uniformdh.Size:]); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = conn.Conn.Write(blob); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Read the public key from the peer.
|
||||
rawPeerPublicKey := make([]byte, uniformdh.Size)
|
||||
if _, err = io.ReadFull(conn.Conn, rawPeerPublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
var peerPublicKey uniformdh.PublicKey
|
||||
if err = peerPublicKey.SetBytes(rawPeerPublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// After retrieving the public key of the other end, each party
|
||||
// completes the DH key exchange and generates a shared-secret for the
|
||||
// session (named SHARED_SECRET).
|
||||
var sharedSecret []byte
|
||||
if sharedSecret, err = uniformdh.Handshake(privateKey, &peerPublicKey); err != nil {
|
||||
return
|
||||
}
|
||||
if err = conn.kdf(sharedSecret); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs3Conn) kdf(sharedSecret []byte) (err error) {
|
||||
// Using that shared-secret each party derives its encryption keys as
|
||||
// follows:
|
||||
//
|
||||
// INIT_SECRET = HMAC(SHARED_SECRET, "Initiator obfuscated data")
|
||||
// RESP_SECRET = HMAC(SHARED_SECRET, "Responder obfuscated data")
|
||||
// INIT_KEY = INIT_SECRET[:KEYLEN]
|
||||
// INIT_COUNTER = INIT_SECRET[KEYLEN:]
|
||||
// RESP_KEY = RESP_SECRET[:KEYLEN]
|
||||
// RESP_COUNTER = RESP_SECRET[KEYLEN:]
|
||||
initHmac := hmac.New(sha256.New, sharedSecret)
|
||||
initHmac.Write([]byte(initiatorKdfString))
|
||||
initSecret := initHmac.Sum(nil)
|
||||
initHmac.Reset()
|
||||
initHmac.Write([]byte(initiatorMagicString))
|
||||
initMagic := initHmac.Sum(nil)
|
||||
|
||||
respHmac := hmac.New(sha256.New, sharedSecret)
|
||||
respHmac.Write([]byte(responderKdfString))
|
||||
respSecret := respHmac.Sum(nil)
|
||||
respHmac.Reset()
|
||||
respHmac.Write([]byte(responderMagicString))
|
||||
respMagic := respHmac.Sum(nil)
|
||||
|
||||
// The INIT_KEY value keys a block cipher (in CTR mode) used to
|
||||
// encrypt values from initiator to responder thereafter. The counter
|
||||
// mode's initial counter value is INIT_COUNTER. The RESP_KEY value
|
||||
// keys a block cipher (in CTR mode) used to encrypt values from
|
||||
// responder to initiator thereafter. That counter mode's initial
|
||||
// counter value is RESP_COUNTER.
|
||||
//
|
||||
// Note: To have this be the last place where the shared secret is used,
|
||||
// also generate the magic value to send/scan for here.
|
||||
var initBlock cipher.Block
|
||||
if initBlock, err = aes.NewCipher(initSecret[:keyLen]); err != nil {
|
||||
return err
|
||||
}
|
||||
initStream := cipher.NewCTR(initBlock, initSecret[keyLen:])
|
||||
|
||||
var respBlock cipher.Block
|
||||
if respBlock, err = aes.NewCipher(respSecret[:keyLen]); err != nil {
|
||||
return err
|
||||
}
|
||||
respStream := cipher.NewCTR(respBlock, respSecret[keyLen:])
|
||||
|
||||
if conn.isInitiator {
|
||||
conn.tx = &cipher.StreamWriter{initStream, conn.Conn, nil}
|
||||
conn.rx = &cipher.StreamReader{respStream, conn.rxBuf}
|
||||
conn.txMagic = initMagic
|
||||
conn.rxMagic = respMagic
|
||||
} else {
|
||||
conn.tx = &cipher.StreamWriter{respStream, conn.Conn, nil}
|
||||
conn.rx = &cipher.StreamReader{initStream, conn.rxBuf}
|
||||
conn.txMagic = respMagic
|
||||
conn.rxMagic = initMagic
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs3Conn) findPeerMagic() error {
|
||||
var hsBuf [maxPadding + sha256.Size]byte
|
||||
for {
|
||||
n, err := conn.Conn.Read(hsBuf[:])
|
||||
if err != nil {
|
||||
// Yes, Read can return partial data and an error, but continuing
|
||||
// past that is nonsensical.
|
||||
return err
|
||||
}
|
||||
conn.rxBuf.Write(hsBuf[:n])
|
||||
|
||||
pos := bytes.Index(conn.rxBuf.Bytes(), conn.rxMagic)
|
||||
if pos == -1 {
|
||||
if conn.rxBuf.Len() >= maxPadding+sha256.Size {
|
||||
return errors.New("failed to find peer magic value")
|
||||
}
|
||||
continue
|
||||
} else if pos > maxPadding {
|
||||
return errors.New("peer sent too much pre-magic-padding")
|
||||
}
|
||||
|
||||
// Discard the padding/MAC.
|
||||
pos += len(conn.rxMagic)
|
||||
_ = conn.rxBuf.Next(pos)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *obfs3Conn) Read(b []byte) (n int, err error) {
|
||||
// If this is the first time we read data post handshake, scan for the
|
||||
// magic value.
|
||||
if conn.rxMagic != nil {
|
||||
if err = conn.findPeerMagic(); err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.rxMagic = nil
|
||||
}
|
||||
|
||||
// If the handshake receive buffer is still present...
|
||||
if conn.rxBuf != nil {
|
||||
// And it is empty...
|
||||
if conn.rxBuf.Len() == 0 {
|
||||
// There is no more trailing data left from the handshake process,
|
||||
// so rewire the cipher.StreamReader to pull data from the network
|
||||
// instead of the temporary receive buffer.
|
||||
conn.rx.R = conn.Conn
|
||||
conn.rxBuf = nil
|
||||
}
|
||||
}
|
||||
|
||||
return conn.rx.Read(b)
|
||||
}
|
||||
|
||||
func (conn *obfs3Conn) Write(b []byte) (n int, err error) {
|
||||
// If this is the first time we write data post handshake, send the
|
||||
// padding/magic value.
|
||||
if conn.txMagic != nil {
|
||||
padLen := csrand.IntRange(0, maxPadding/2)
|
||||
blob := make([]byte, padLen+len(conn.txMagic))
|
||||
if err = csrand.Bytes(blob[:padLen]); err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
copy(blob[padLen:], conn.txMagic)
|
||||
if _, err = conn.Conn.Write(blob); err != nil {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
conn.txMagic = nil
|
||||
}
|
||||
|
||||
return conn.tx.Write(b)
|
||||
}
|
||||
|
||||
|
||||
var _ base.ClientFactory = (*obfs3ClientFactory)(nil)
|
||||
var _ base.ServerFactory = (*obfs3ServerFactory)(nil)
|
||||
var _ base.Transport = (*Transport)(nil)
|
||||
var _ net.Conn = (*obfs3Conn)(nil)
|
@ -0,0 +1,579 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
// Package obfs4 provides an implementation of the Tor Project's obfs4
|
||||
// obfuscation protocol.
|
||||
package obfs4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.torproject.org/pluggable-transports/goptlib.git"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/probdist"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/replayfilter"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4/framing"
|
||||
)
|
||||
|
||||
const (
|
||||
transportName = "obfs4"
|
||||
|
||||
nodeIDArg = "node-id"
|
||||
publicKeyArg = "public-key"
|
||||
privateKeyArg = "private-key"
|
||||
seedArg = "drbg-seed"
|
||||
|
||||
seedLength = 32
|
||||
headerLength = framing.FrameOverhead + packetOverhead
|
||||
clientHandshakeTimeout = time.Duration(60) * time.Second
|
||||
serverHandshakeTimeout = time.Duration(30) * time.Second
|
||||
replayTTL = time.Duration(3) * time.Hour
|
||||
|
||||
// Use a ScrambleSuit style biased probability table.
|
||||
biasedDist = false
|
||||
|
||||
// Use IAT obfuscation.
|
||||
iatObfuscation = false
|
||||
|
||||
// Maximum IAT delay (100 usec increments).
|
||||
maxIATDelay = 100
|
||||
|
||||
maxCloseDelayBytes = maxHandshakeLength
|
||||
maxCloseDelay = 60
|
||||
)
|
||||
|
||||
type obfs4ClientArgs struct {
|
||||
nodeID *ntor.NodeID
|
||||
publicKey *ntor.PublicKey
|
||||
sessionKey *ntor.Keypair
|
||||
}
|
||||
|
||||
// Transport is the obfs4 implementation of the base.Transport interface.
|
||||
type Transport struct{}
|
||||
|
||||
// Name returns the name of the obfs4 transport protocol.
|
||||
func (t *Transport) Name() string {
|
||||
return transportName
|
||||
}
|
||||
|
||||
// ClientFactory returns a new obfs4ClientFactory instance.
|
||||
func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
|
||||
cf := &obfs4ClientFactory{transport: t}
|
||||
return cf, nil
|
||||
}
|
||||
|
||||
// ServerFactory returns a new obfs4ServerFactory instance.
|
||||
func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
|
||||
var err error
|
||||
|
||||
var st *obfs4ServerState
|
||||
if st, err = serverStateFromArgs(stateDir, args); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var iatSeed *drbg.Seed
|
||||
if iatObfuscation {
|
||||
iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
|
||||
iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Store the arguments that should appear in our descriptor for the clients.
|
||||
ptArgs := pt.Args{}
|
||||
ptArgs.Add(nodeIDArg, st.nodeID.Base64())
|
||||
ptArgs.Add(publicKeyArg, st.identityKey.Public().Base64())
|
||||
|
||||
// Initialize the replay filter.
|
||||
filter, err := replayfilter.New(replayTTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize the close thresholds for failed connections.
|
||||
drbg, err := drbg.NewHashDrbg(st.drbgSeed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rng := rand.New(drbg)
|
||||
|
||||
sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
type obfs4ClientFactory struct {
|
||||
transport base.Transport
|
||||
}
|
||||
|
||||
func (cf *obfs4ClientFactory) Transport() base.Transport {
|
||||
return cf.transport
|
||||
}
|
||||
|
||||
func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
|
||||
var err error
|
||||
|
||||
// Handle the arguments.
|
||||
nodeIDStr, ok := args.Get(nodeIDArg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
|
||||
}
|
||||
var nodeID *ntor.NodeID
|
||||
if nodeID, err = ntor.NodeIDFromBase64(nodeIDStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
publicKeyStr, ok := args.Get(publicKeyArg)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
|
||||
}
|
||||
var publicKey *ntor.PublicKey
|
||||
if publicKey, err = ntor.PublicKeyFromBase64(publicKeyStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate the session key pair before connectiong to hide the Elligator2
|
||||
// rejection sampling from network observers.
|
||||
sessionKey, err := ntor.NewKeypair(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &obfs4ClientArgs{nodeID, publicKey, sessionKey}, nil
|
||||
}
|
||||
|
||||
func (cf *obfs4ClientFactory) WrapConn(conn net.Conn, args interface{}) (net.Conn, error) {
|
||||
ca, ok := args.(*obfs4ClientArgs)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid argument type for args")
|
||||
}
|
||||
|
||||
return newObfs4ClientConn(conn, ca)
|
||||
}
|
||||
|
||||
type obfs4ServerFactory struct {
|
||||
transport base.Transport
|
||||
args *pt.Args
|
||||
|
||||
nodeID *ntor.NodeID
|
||||
identityKey *ntor.Keypair
|
||||
lenSeed *drbg.Seed
|
||||
iatSeed *drbg.Seed
|
||||
replayFilter *replayfilter.ReplayFilter
|
||||
|
||||
closeDelayBytes int
|
||||
closeDelay int
|
||||
}
|
||||
|
||||
func (sf *obfs4ServerFactory) Transport() base.Transport {
|
||||
return sf.transport
|
||||
}
|
||||
|
||||
func (sf *obfs4ServerFactory) Args() *pt.Args {
|
||||
return sf.args
|
||||
}
|
||||
|
||||
func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
|
||||
// Not much point in having a separate newObfs4ServerConn routine when
|
||||
// wrapping requires using values from the factory instance.
|
||||
|
||||
// Generate the session keypair *before* consuming data from the peer, to
|
||||
// attempt to mask the rejection sampling due to use of Elligator2. This
|
||||
// might be futile, but the timing differential isn't very large on modern
|
||||
// hardware, and there are far easier statistical attacks that can be
|
||||
// mounted as a distinguisher.
|
||||
sessionKey, err := ntor.NewKeypair(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist)
|
||||
var iatDist *probdist.WeightedDist
|
||||
if sf.iatSeed != nil {
|
||||
iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
|
||||
}
|
||||
|
||||
c := &obfs4Conn{conn, true, lenDist, iatDist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
|
||||
|
||||
startTime := time.Now()
|
||||
|
||||
if err = c.serverHandshake(sf, sessionKey); err != nil {
|
||||
c.closeAfterDelay(sf, startTime)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type obfs4Conn struct {
|
||||
net.Conn
|
||||
|
||||
isServer bool
|
||||
|
||||
lenDist *probdist.WeightedDist
|
||||
iatDist *probdist.WeightedDist
|
||||
|
||||
receiveBuffer *bytes.Buffer
|
||||
receiveDecodedBuffer *bytes.Buffer
|
||||
|
||||
encoder *framing.Encoder
|
||||
decoder *framing.Decoder
|
||||
}
|
||||
|
||||
func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
|
||||
// Generate the initial protocol polymorphism distribution(s).
|
||||
var seed *drbg.Seed
|
||||
if seed, err = drbg.NewSeed(); err != nil {
|
||||
return
|
||||
}
|
||||
lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
|
||||
var iatDist *probdist.WeightedDist
|
||||
if iatObfuscation {
|
||||
var iatSeed *drbg.Seed
|
||||
iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
|
||||
if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil {
|
||||
return
|
||||
}
|
||||
iatDist = probdist.New(iatSeed, 0, maxIATDelay, biasedDist)
|
||||
}
|
||||
|
||||
// Allocate the client structure.
|
||||
c = &obfs4Conn{conn, false, lenDist, iatDist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
|
||||
|
||||
// Start the handshake timeout.
|
||||
deadline := time.Now().Add(clientHandshakeTimeout)
|
||||
if err = conn.SetDeadline(deadline); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Stop the handshake timeout.
|
||||
if err = conn.SetDeadline(time.Time{}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error {
|
||||
if conn.isServer {
|
||||
return fmt.Errorf("clientHandshake called on server connection")
|
||||
}
|
||||
|
||||
// Generate and send the client handshake.
|
||||
hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey)
|
||||
blob, err := hs.generateHandshake()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = conn.Conn.Write(blob); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Consume the server handshake.
|
||||
var hsBuf [maxHandshakeLength]byte
|
||||
for {
|
||||
var n int
|
||||
if n, err = conn.Conn.Read(hsBuf[:]); err != nil {
|
||||
// The Read() could have returned data and an error, but there is
|
||||
// no point in continuing on an EOF or whatever.
|
||||
return err
|
||||
}
|
||||
conn.receiveBuffer.Write(hsBuf[:n])
|
||||
|
||||
var seed []byte
|
||||
n, seed, err = hs.parseServerHandshake(conn.receiveBuffer.Bytes())
|
||||
if err == ErrMarkNotFoundYet {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = conn.receiveBuffer.Next(n)
|
||||
|
||||
// Use the derived key material to intialize the link crypto.
|
||||
okm := ntor.Kdf(seed, framing.KeyLength*2)
|
||||
conn.encoder = framing.NewEncoder(okm[:framing.KeyLength])
|
||||
conn.decoder = framing.NewDecoder(okm[framing.KeyLength:])
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.Keypair) (err error) {
|
||||
if !conn.isServer {
|
||||
return fmt.Errorf("serverHandshake called on client connection")
|
||||
}
|
||||
|
||||
// Generate the server handshake, and arm the base timeout.
|
||||
hs := newServerHandshake(sf.nodeID, sf.identityKey, sessionKey)
|
||||
if err = conn.Conn.SetDeadline(time.Now().Add(serverHandshakeTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume the client handshake.
|
||||
var hsBuf [maxHandshakeLength]byte
|
||||
for {
|
||||
var n int
|
||||
if n, err = conn.Conn.Read(hsBuf[:]); err != nil {
|
||||
// The Read() could have returned data and an error, but there is
|
||||
// no point in continuing on an EOF or whatever.
|
||||
return
|
||||
}
|
||||
conn.receiveBuffer.Write(hsBuf[:n])
|
||||
|
||||
var seed []byte
|
||||
seed, err = hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes())
|
||||
if err == ErrMarkNotFoundYet {
|
||||
continue
|
||||
} else if err != nil {
|
||||
return
|
||||
}
|
||||
conn.receiveBuffer.Reset()
|
||||
|
||||
if err = conn.Conn.SetDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Use the derived key material to intialize the link crypto.
|
||||
okm := ntor.Kdf(seed, framing.KeyLength*2)
|
||||
conn.encoder = framing.NewEncoder(okm[framing.KeyLength:])
|
||||
conn.decoder = framing.NewDecoder(okm[:framing.KeyLength])
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
// Since the current and only implementation always sends a PRNG seed for
|
||||
// the length obfuscation, this makes the amount of data received from the
|
||||
// server inconsistent with the length sent from the client.
|
||||
//
|
||||
// Rebalance this by tweaking the client mimimum padding/server maximum
|
||||
// padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed
|
||||
// as part of the server response). See inlineSeedFrameLength in
|
||||
// handshake_ntor.go.
|
||||
|
||||
// Generate/send the response.
|
||||
var blob []byte
|
||||
blob, err = hs.generateHandshake()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var frameBuf bytes.Buffer
|
||||
_, err = frameBuf.Write(blob)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Send the PRNG seed as the first packet.
|
||||
if err = conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil {
|
||||
return
|
||||
}
|
||||
if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) Read(b []byte) (n int, err error) {
|
||||
// If there is no payload from the previous Read() calls, consume data off
|
||||
// the network. Not all data received is guaranteed to be usable payload,
|
||||
// so do this in a loop till data is present or an error occurs.
|
||||
for conn.receiveDecodedBuffer.Len() == 0 {
|
||||
err = conn.readPackets()
|
||||
if err == framing.ErrAgain {
|
||||
// Don't proagate this back up the call stack if we happen to break
|
||||
// out of the loop.
|
||||
err = nil
|
||||
continue
|
||||
} else if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Even if err is set, attempt to do the read anyway so that all decoded
|
||||
// data gets relayed before the connection is torn down.
|
||||
if conn.receiveDecodedBuffer.Len() > 0 {
|
||||
var berr error
|
||||
n, berr = conn.receiveDecodedBuffer.Read(b)
|
||||
if err == nil {
|
||||
// Only propagate berr if there are not more important (fatal)
|
||||
// errors from the network/crypto/packet processing.
|
||||
err = berr
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) Write(b []byte) (n int, err error) {
|
||||
chopBuf := bytes.NewBuffer(b)
|
||||
var payload [maxPacketPayloadLength]byte
|
||||
var frameBuf bytes.Buffer
|
||||
|
||||
// Chop the pending data into payload frames.
|
||||
for chopBuf.Len() > 0 {
|
||||
// Send maximum sized frames.
|
||||
rdLen := 0
|
||||
rdLen, err = chopBuf.Read(payload[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if rdLen == 0 {
|
||||
panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
|
||||
}
|
||||
n += rdLen
|
||||
|
||||
err = conn.makePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the length obfuscation padding. In theory, this could be inlined
|
||||
// with the last chopped packet for certain (most?) payload lenghts, but
|
||||
// this is simpler.
|
||||
|
||||
if err = conn.padBurst(&frameBuf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Write the pending data onto the network. Partial writes are fatal,
|
||||
// because the frame encoder state is advanced, and the code doesn't keep
|
||||
// frameBuf around. In theory, write timeouts and whatnot could be
|
||||
// supported if this wasn't the case, but that complicates the code.
|
||||
|
||||
if conn.iatDist != nil {
|
||||
var iatFrame [framing.MaximumSegmentLength]byte
|
||||
for frameBuf.Len() > 0 {
|
||||
iatWrLen := 0
|
||||
iatWrLen, err = frameBuf.Read(iatFrame[:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else if iatWrLen == 0 {
|
||||
panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
|
||||
}
|
||||
|
||||
// Calculate the delay. The delay resolution is 100 usec, leading
|
||||
// to a maximum delay of 10 msec.
|
||||
iatDelta := time.Duration(conn.iatDist.Sample() * 100)
|
||||
|
||||
// Write then sleep.
|
||||
_, err = conn.Conn.Write(iatFrame[:iatWrLen])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
time.Sleep(iatDelta * time.Microsecond)
|
||||
}
|
||||
} else {
|
||||
_, err = conn.Conn.Write(frameBuf.Bytes())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) SetDeadline(t time.Time) error {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
|
||||
return syscall.ENOTSUP
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) {
|
||||
// I-it's not like I w-wanna handshake with you or anything. B-b-baka!
|
||||
defer conn.Conn.Close()
|
||||
|
||||
delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout
|
||||
deadline := startTime.Add(delay)
|
||||
if time.Now().After(deadline) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := conn.Conn.SetReadDeadline(deadline); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Consume and discard data on this connection until either the specified
|
||||
// interval passes or a certain size has been reached.
|
||||
discarded := 0
|
||||
var buf [framing.MaximumSegmentLength]byte
|
||||
for discarded < int(sf.closeDelayBytes) {
|
||||
n, err := conn.Conn.Read(buf[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
discarded += n
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *obfs4Conn) padBurst(burst *bytes.Buffer) (err error) {
|
||||
tailLen := burst.Len() % framing.MaximumSegmentLength
|
||||
toPadTo := conn.lenDist.Sample()
|
||||
|
||||
padLen := 0
|
||||
if toPadTo >= tailLen {
|
||||
padLen = toPadTo - tailLen
|
||||
} else {
|
||||
padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
|
||||
}
|
||||
|
||||
if padLen > headerLength {
|
||||
err = conn.makePacket(burst, packetTypePayload, []byte{},
|
||||
uint16(padLen-headerLength))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if padLen > 0 {
|
||||
err = conn.makePacket(burst, packetTypePayload, []byte{},
|
||||
maxPacketPayloadLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = conn.makePacket(burst, packetTypePayload, []byte{},
|
||||
uint16(padLen))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var _ base.ClientFactory = (*obfs4ClientFactory)(nil)
|
||||
var _ base.ServerFactory = (*obfs4ServerFactory)(nil)
|
||||
var _ base.Transport = (*Transport)(nil)
|
||||
var _ net.Conn = (*obfs4Conn)(nil)
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package obfs4
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"git.torproject.org/pluggable-transports/goptlib.git"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/csrand"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/drbg"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/common/ntor"
|
||||
)
|
||||
|
||||
const (
|
||||
stateFile = "obfs4_state.json"
|
||||
)
|
||||
|
||||
type jsonServerState struct {
|
||||
NodeID string `json:"node-id"`
|
||||
PrivateKey string `json:"private-key"`
|
||||
PublicKey string `json:"public-key"`
|
||||
DrbgSeed string `json:"drbgSeed"`
|
||||
}
|
||||
|
||||
type obfs4ServerState struct {
|
||||
nodeID *ntor.NodeID
|
||||
identityKey *ntor.Keypair
|
||||
drbgSeed *drbg.Seed
|
||||
}
|
||||
|
||||
func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) {
|
||||
var js jsonServerState
|
||||
var nodeIDOk, privKeyOk, seedOk bool
|
||||
|
||||
js.NodeID, nodeIDOk = args.Get(nodeIDArg)
|
||||
js.PrivateKey, privKeyOk = args.Get(privateKeyArg)
|
||||
js.DrbgSeed, seedOk = args.Get(seedArg)
|
||||
|
||||
if !privKeyOk && !nodeIDOk && !seedOk {
|
||||
if err := jsonServerStateFromFile(stateDir, &js); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !privKeyOk {
|
||||
return nil, fmt.Errorf("missing argument '%s'", privateKeyArg)
|
||||
} else if !nodeIDOk {
|
||||
return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
|
||||
} else if !seedOk {
|
||||
return nil, fmt.Errorf("missing argument '%s'", seedArg)
|
||||
}
|
||||
|
||||
return serverStateFromJSONServerState(&js)
|
||||
}
|
||||
|
||||
func serverStateFromJSONServerState(js *jsonServerState) (*obfs4ServerState, error) {
|
||||
var err error
|
||||
|
||||
st := new(obfs4ServerState)
|
||||
if st.nodeID, err = ntor.NodeIDFromBase64(js.NodeID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if st.identityKey, err = ntor.KeypairFromBase64(js.PrivateKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rawSeed []byte
|
||||
if rawSeed, err = base64.StdEncoding.DecodeString(js.DrbgSeed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if st.drbgSeed, err = drbg.SeedFromBytes(rawSeed); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
func jsonServerStateFromFile(stateDir string, js *jsonServerState) error {
|
||||
f, err := ioutil.ReadFile(path.Join(stateDir, stateFile))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err = newJSONServerState(stateDir, js); err == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(f, js); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newJSONServerState(stateDir string, js *jsonServerState) (err error) {
|
||||
// Generate everything a server needs, using the cryptographic PRNG.
|
||||
var st obfs4ServerState
|
||||
rawID := make([]byte, ntor.NodeIDLength)
|
||||
if err = csrand.Bytes(rawID); err != nil {
|
||||
return
|
||||
}
|
||||
if st.nodeID, err = ntor.NewNodeID(rawID); err != nil {
|
||||
return
|
||||
}
|
||||
if st.identityKey, err = ntor.NewKeypair(false); err != nil {
|
||||
return
|
||||
}
|
||||
if st.drbgSeed, err = drbg.NewSeed(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Encode it into JSON format and write the state file.
|
||||
js.NodeID = st.nodeID.Base64()
|
||||
js.PrivateKey = st.identityKey.Private().Base64()
|
||||
js.PublicKey = st.identityKey.Public().Base64()
|
||||
js.DrbgSeed = st.drbgSeed.Base64()
|
||||
|
||||
var encoded []byte
|
||||
if encoded, err = json.Marshal(js); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* 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 transports provides a interface to query supported pluggable
|
||||
// transports.
|
||||
package transports
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/base"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs2"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs3"
|
||||
"git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4"
|
||||
)
|
||||
|
||||
var transportMapLock sync.Mutex
|
||||
var transportMap map[string]base.Transport
|
||||
|
||||
// Register registers a transport protocol.
|
||||
func Register(transport base.Transport) error {
|
||||
transportMapLock.Lock()
|
||||
defer transportMapLock.Unlock()
|
||||
|
||||
name := transport.Name()
|
||||
_, registered := transportMap[name]
|
||||
if registered {
|
||||
return fmt.Errorf("transport '%s' already registered", name)
|
||||
}
|
||||
transportMap[name] = transport
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Transports returns the list of registered transport protocols.
|
||||
func Transports() []string {
|
||||
transportMapLock.Lock()
|
||||
defer transportMapLock.Unlock()
|
||||
|
||||
var ret []string
|
||||
for name := range transportMap {
|
||||
ret = append(ret, name)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Get returns a transport protocol implementation by name.
|
||||
func Get(name string) base.Transport {
|
||||
transportMapLock.Lock()
|
||||
defer transportMapLock.Unlock()
|
||||
|
||||
t := transportMap[name]
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Initialize the transport list.
|
||||
transportMap = make(map[string]base.Transport)
|
||||
|
||||
// Register all the currently supported transports.
|
||||
Register(new(obfs2.Transport))
|
||||
Register(new(obfs3.Transport))
|
||||
Register(new(obfs4.Transport))
|
||||
}
|
Loading…
Reference in New Issue