summaryrefslogtreecommitdiff
path: root/obfs4.go
diff options
context:
space:
mode:
Diffstat (limited to 'obfs4.go')
-rw-r--r--obfs4.go758
1 files changed, 0 insertions, 758 deletions
diff --git a/obfs4.go b/obfs4.go
deleted file mode 100644
index 4cc159c..0000000
--- a/obfs4.go
+++ /dev/null
@@ -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 : */