summaryrefslogtreecommitdiff
path: root/transports
diff options
context:
space:
mode:
authorBrandon Wiley <brandon@blanu.net>2016-11-15 14:52:04 -0600
committerBrandon Wiley <brandon@blanu.net>2016-11-15 14:52:04 -0600
commit8a7de7e1e252a73d786bafab042eedde8c025ad6 (patch)
treeeefeca82da3a8f61bcc19ba2d4dd6c43952c77b7 /transports
parent93cb566c11b9c968677786bc6b5cf01ac06931e6 (diff)
Removed transports from shapeshifter-dispatcher (now located in shapeshifter-transports)
Diffstat (limited to 'transports')
-rw-r--r--transports/base/base.go90
-rw-r--r--transports/meeklite/base.go89
-rw-r--r--transports/meeklite/meek.go373
-rw-r--r--transports/obfs2/obfs2.go374
-rw-r--r--transports/obfs3/obfs3.go366
-rw-r--r--transports/obfs4/framing/framing.go306
-rw-r--r--transports/obfs4/framing/framing_test.go169
-rw-r--r--transports/obfs4/handshake_ntor.go425
-rw-r--r--transports/obfs4/handshake_ntor_test.go248
-rw-r--r--transports/obfs4/obfs4.go646
-rw-r--r--transports/obfs4/packet.go176
-rw-r--r--transports/obfs4/statefile.go252
-rw-r--r--transports/scramblesuit/base.go99
-rw-r--r--transports/scramblesuit/conn.go523
-rw-r--r--transports/scramblesuit/handshake_ticket.go228
-rw-r--r--transports/scramblesuit/handshake_uniformdh.go173
-rw-r--r--transports/scramblesuit/hkdf_expand.go67
17 files changed, 0 insertions, 4604 deletions
diff --git a/transports/base/base.go b/transports/base/base.go
deleted file mode 100644
index bb0902e..0000000
--- a/transports/base/base.go
+++ /dev/null
@@ -1,90 +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 base provides the common interface that each supported transport
-// protocol must implement.
-package base
-
-import (
- "net"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
-)
-
-type DialFunc func(string, string) (net.Conn, error)
-
-// 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)
-
- // Dial creates an outbound net.Conn, and does whatever is required
- // (eg: handshaking) to get the connection to the point where it is
- // ready to relay data.
- Dial(network, address string, dialFn DialFunc, 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)
-}
diff --git a/transports/meeklite/base.go b/transports/meeklite/base.go
deleted file mode 100644
index 6401885..0000000
--- a/transports/meeklite/base.go
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2015, 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 meeklite provides an implementation of the Meek circumvention
-// protocol. Only a client implementation is provided, and no effort is
-// made to normalize the TLS fingerprint.
-//
-// It borrows quite liberally from the real meek-client code.
-package meeklite
-
-import (
- "fmt"
- "net"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
- "github.com/OperatorFoundation/obfs4/transports/base"
-)
-
-const transportName = "meek_lite"
-
-// Transport is the Meek implementation of the base.Transport interface.
-type Transport struct{}
-
-// Name returns the name of the Meek transport protocol.
-func (t *Transport) Name() string {
- return transportName
-}
-
-// ClientFactory returns a new meekClientFactory instance.
-func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
- cf := &meekClientFactory{transport: t}
- return cf, nil
-}
-
-// ServerFactory will one day return a new meekServerFactory instance.
-func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
- // TODO: Fill this in eventually, though for servers people should
- // just use the real thing.
- return nil, fmt.Errorf("server not supported")
-}
-
-type meekClientFactory struct {
- transport base.Transport
-}
-
-func (cf *meekClientFactory) Transport() base.Transport {
- return cf.transport
-}
-
-func (cf *meekClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
- return newClientArgs(args)
-}
-
-func (cf *meekClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
- // Validate args before opening outgoing connection.
- ca, ok := args.(*meekClientArgs)
- if !ok {
- return nil, fmt.Errorf("invalid argument type for args")
- }
-
- return newMeekConn(network, addr, dialFn, ca)
-}
-
-var _ base.ClientFactory = (*meekClientFactory)(nil)
-var _ base.Transport = (*Transport)(nil)
diff --git a/transports/meeklite/meek.go b/transports/meeklite/meek.go
deleted file mode 100644
index 79012ac..0000000
--- a/transports/meeklite/meek.go
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright (c) 2015, 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 meeklite
-
-import (
- "bytes"
- "crypto/rand"
- "crypto/sha256"
- "encoding/hex"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- gourl "net/url"
- "runtime"
- "sync"
- "time"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
- "github.com/OperatorFoundation/obfs4/transports/base"
-)
-
-const (
- urlArg = "url"
- frontArg = "front"
-
- maxChanBacklog = 16
-
- // Constants shamelessly stolen from meek-client.go...
- maxPayloadLength = 0x10000
- initPollInterval = 100 * time.Millisecond
- maxPollInterval = 5 * time.Second
- pollIntervalMultiplier = 1.5
- maxRetries = 10
- retryDelay = 30 * time.Second
-)
-
-var (
- // ErrNotSupported is the error returned for a unsupported operation.
- ErrNotSupported = errors.New("meek_lite: operation not supported")
-
- loopbackAddr = net.IPv4(127, 0, 0, 1)
-)
-
-type meekClientArgs struct {
- url *gourl.URL
- front string
-}
-
-func (ca *meekClientArgs) Network() string {
- return transportName
-}
-
-func (ca *meekClientArgs) String() string {
- return transportName + ":" + ca.front + ":" + ca.url.String()
-}
-
-func newClientArgs(args *pt.Args) (ca *meekClientArgs, err error) {
- ca = &meekClientArgs{}
-
- // Parse the URL argument.
- str, ok := args.Get(urlArg)
- if !ok {
- return nil, fmt.Errorf("missing argument '%s'", urlArg)
- }
- ca.url, err = gourl.Parse(str)
- if err != nil {
- return nil, fmt.Errorf("malformed url: '%s'", str)
- }
- switch ca.url.Scheme {
- case "http", "https":
- default:
- return nil, fmt.Errorf("invalid scheme: '%s'", ca.url.Scheme)
- }
-
- // Parse the (optional) front argument.
- ca.front, _ = args.Get(frontArg)
-
- return ca, nil
-}
-
-type meekConn struct {
- sync.Mutex
-
- args *meekClientArgs
- sessionID string
- transport *http.Transport
-
- workerRunning bool
- workerWrChan chan []byte
- workerRdChan chan []byte
- workerCloseChan chan bool
- rdBuf *bytes.Buffer
-}
-
-func (c *meekConn) Read(p []byte) (n int, err error) {
- // If there is data left over from the previous read,
- // service the request using the buffered data.
- if c.rdBuf != nil {
- if c.rdBuf.Len() == 0 {
- panic("empty read buffer")
- }
- n, err = c.rdBuf.Read(p)
- if c.rdBuf.Len() == 0 {
- c.rdBuf = nil
- }
- return
- }
-
- // Wait for the worker to enqueue more incoming data.
- b, ok := <-c.workerRdChan
- if !ok {
- // Close() was called and the worker's shutting down.
- return 0, io.ErrClosedPipe
- }
-
- // Ew, an extra copy, but who am I kidding, it's meek.
- buf := bytes.NewBuffer(b)
- n, err = buf.Read(p)
- if buf.Len() > 0 {
- // If there's data pending, stash the buffer so the next
- // Read() call will use it to fulfuill the Read().
- c.rdBuf = buf
- }
- return
-}
-
-func (c *meekConn) Write(b []byte) (n int, err error) {
- // Check to see if the connection is actually open.
- c.Lock()
- closed := !c.workerRunning
- c.Unlock()
- if closed {
- return 0, io.ErrClosedPipe
- }
-
- if len(b) == 0 {
- return 0, nil
- }
-
- // Copy the data to be written to a new slice, since
- // we return immediately after queuing and the peer can
- // happily reuse `b` before data has been sent.
- toWrite := len(b)
- b2 := make([]byte, toWrite)
- copy(b2, b)
- if ok := c.enqueueWrite(b2); !ok {
- // Technically we did enqueue data, but the worker's
- // got closed out from under us.
- return 0, io.ErrClosedPipe
- }
- runtime.Gosched()
- return len(b), nil
-}
-
-func (c *meekConn) Close() error {
- // Ensure that we do this once and only once.
- c.Lock()
- defer c.Unlock()
- if !c.workerRunning {
- return nil
- }
-
- // Tear down the worker.
- c.workerRunning = false
- c.workerCloseChan <- true
-
- return nil
-}
-
-func (c *meekConn) LocalAddr() net.Addr {
- return &net.IPAddr{IP: loopbackAddr}
-}
-
-func (c *meekConn) RemoteAddr() net.Addr {
- return c.args
-}
-
-func (c *meekConn) SetDeadline(t time.Time) error {
- return ErrNotSupported
-}
-
-func (c *meekConn) SetReadDeadline(t time.Time) error {
- return ErrNotSupported
-}
-
-func (c *meekConn) SetWriteDeadline(t time.Time) error {
- return ErrNotSupported
-}
-
-func (c *meekConn) enqueueWrite(b []byte) (ok bool) {
- defer func() { recover() }()
- c.workerWrChan <- b
- return true
-}
-
-func (c *meekConn) roundTrip(sndBuf []byte) (recvBuf []byte, err error) {
- var req *http.Request
- var resp *http.Response
-
- for retries := 0; retries < maxRetries; retries++ {
- url := *c.args.url
- host := url.Host
- if c.args.front != "" {
- url.Host = c.args.front
- }
- req, err = http.NewRequest("POST", url.String(), bytes.NewReader(sndBuf))
- if err != nil {
- return nil, err
- }
- if c.args.front != "" {
- req.Host = host
- }
- req.Header.Set("X-Session-Id", c.sessionID)
- req.Header.Set("User-Agent", "")
-
- resp, err = c.transport.RoundTrip(req)
- if err != nil {
- return nil, err
- }
- if resp.StatusCode != http.StatusOK {
- err = fmt.Errorf("status code was %d, not %d", resp.StatusCode, http.StatusOK)
- time.Sleep(retryDelay)
- } else {
- defer resp.Body.Close()
- recvBuf, err = ioutil.ReadAll(io.LimitReader(resp.Body, maxPayloadLength))
- return
- }
- }
- return
-}
-
-func (c *meekConn) ioWorker() {
- interval := initPollInterval
- var sndBuf, leftBuf []byte
-loop:
-
- for {
- sndBuf = nil
- select {
- case <-time.After(interval):
- // If the poll interval has elapsed, issue a request.
- case sndBuf = <-c.workerWrChan:
- // If there is data pending a send, issue a request.
- case _ = <-c.workerCloseChan:
- break loop
- }
-
- // Combine short writes as long as data is available to be
- // sent immediately and it will not put us over the max
- // payload limit. Any excess data is stored and dispatched
- // as the next request).
- sndBuf = append(leftBuf, sndBuf...)
- wrSz := len(sndBuf)
- for len(c.workerWrChan) > 0 && wrSz < maxPayloadLength {
- b := <-c.workerWrChan
- sndBuf = append(sndBuf, b...)
- wrSz = len(sndBuf)
- }
- if wrSz > maxPayloadLength {
- wrSz = maxPayloadLength
- }
-
- // Issue a request.
- rdBuf, err := c.roundTrip(sndBuf[:wrSz])
- if err != nil {
- // Welp, something went horrifically wrong.
- break loop
- }
-
- // Stash the remaining payload if any.
- leftBuf = sndBuf[wrSz:] // Store the remaining data
- if len(leftBuf) == 0 {
- leftBuf = nil
- }
-
- // Determine the next poll interval.
- if len(rdBuf) > 0 {
- // Received data, enqueue the read.
- c.workerRdChan <- rdBuf
-
- // And poll immediately.
- interval = 0
- } else if wrSz > 0 {
- // Sent data, poll immediately.
- interval = 0
- } else if interval == 0 {
- // Neither sent nor received data, initialize the delay.
- interval = initPollInterval
- } else {
- // Apply a multiplicative backoff.
- interval = time.Duration(float64(interval) * pollIntervalMultiplier)
- if interval > maxPollInterval {
- interval = maxPollInterval
- }
- }
-
- runtime.Gosched()
- }
-
- // Unblock callers waiting in Read() for data that will never arrive,
- // and callers waiting in Write() for data that will never get sent.
- close(c.workerRdChan)
- close(c.workerWrChan)
-
- // In case the close was done on an error condition, update the state
- // variable so that further calls to Write() will fail.
- c.Lock()
- defer c.Unlock()
- c.workerRunning = false
-}
-
-func newMeekConn(network, addr string, dialFn base.DialFunc, ca *meekClientArgs) (net.Conn, error) {
- id, err := newSessionID()
- if err != nil {
- return nil, err
- }
-
- tr := &http.Transport{Dial: dialFn}
- conn := &meekConn{
- args: ca,
- sessionID: id,
- transport: tr,
- workerRunning: true,
- workerWrChan: make(chan []byte, maxChanBacklog),
- workerRdChan: make(chan []byte, maxChanBacklog),
- workerCloseChan: make(chan bool),
- }
-
- // Start the I/O worker.
- go conn.ioWorker()
-
- return conn, nil
-}
-
-func newSessionID() (string, error) {
- var b [64]byte
- if _, err := rand.Read(b[:]); err != nil {
- return "", err
- }
- h := sha256.Sum256(b[:])
- return hex.EncodeToString(h[:16]), nil
-}
-
-var _ net.Conn = (*meekConn)(nil)
-var _ net.Addr = (*meekClientArgs)(nil)
diff --git a/transports/obfs2/obfs2.go b/transports/obfs2/obfs2.go
deleted file mode 100644
index 8bbdbaa..0000000
--- a/transports/obfs2/obfs2.go
+++ /dev/null
@@ -1,374 +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 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"
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/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) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
- conn, err := dialFn(network, addr)
- if err != nil {
- return nil, err
- }
- dialConn := conn
- if conn, err = newObfs2ClientConn(conn); err != nil {
- dialConn.Close()
- return nil, err
- }
- return conn, nil
-}
-
-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() 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 err
- }
- 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 err
- }
- }
-
- // 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))
- txBlock, err := aes.NewCipher(padKey)
- if err != nil {
- return err
- }
- txStream := cipher.NewCTR(txBlock, padIV)
- conn.tx = &cipher.StreamWriter{S: txStream, W: conn.Conn}
- if _, err := conn.Conn.Write(seed[:]); err != nil {
- return err
- }
- if _, err := conn.Write(hsBlob); err != nil {
- return err
- }
-
- // 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 err
- }
- var peerPadMagic []byte
- if conn.isInitiator {
- peerPadMagic = []byte(responderPadString)
- } else {
- peerPadMagic = []byte(initiatorPadString)
- }
- peerKey, peerIV := hsKdf(peerPadMagic, peerSeed[:], !conn.isInitiator)
- rxBlock, err := aes.NewCipher(peerKey)
- if err != nil {
- return err
- }
- rxStream := cipher.NewCTR(rxBlock, peerIV)
- conn.rx = &cipher.StreamReader{S: rxStream, R: conn.Conn}
- hsHdr := make([]byte, hsLen)
- if _, err := io.ReadFull(conn, hsHdr[:]); err != nil {
- return err
- }
-
- // 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 {
- return fmt.Errorf("invalid magic value: %x", peerMagic)
- }
- padLen = binary.BigEndian.Uint32(hsHdr[4:8])
- if padLen > maxPadding {
- return fmt.Errorf("padlen too long: %d", padLen)
- }
-
- // 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 err
- }
-
- // Derive the actual keys.
- if err := conn.kdf(seed[:], peerSeed[:]); err != nil {
- return err
- }
-
- return nil
-}
-
-func (conn *obfs2Conn) kdf(seed, peerSeed []byte) 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)
- initBlock, err := aes.NewCipher(initKey)
- if err != nil {
- return err
- }
- initStream := cipher.NewCTR(initBlock, initIV)
-
- respKey, respIV := hsKdf([]byte(responderKdfString), combSeed, false)
- respBlock, err := aes.NewCipher(respKey)
- if err != nil {
- return err
- }
- 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 nil
-}
-
-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)
diff --git a/transports/obfs3/obfs3.go b/transports/obfs3/obfs3.go
deleted file mode 100644
index c872129..0000000
--- a/transports/obfs3/obfs3.go
+++ /dev/null
@@ -1,366 +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 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"
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/common/uniformdh"
- "github.com/OperatorFoundation/obfs4/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) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
- conn, err := dialFn(network, addr)
- if err != nil {
- return nil, err
- }
- dialConn := conn
- if conn, err = newObfs3ClientConn(conn); err != nil {
- dialConn.Close()
- return nil, err
- }
- return conn, nil
-}
-
-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() 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)
- privateKey, err := uniformdh.GenerateKey(csrand.Reader)
- if err != nil {
- return err
- }
- padLen := csrand.IntRange(0, maxPadding/2)
- blob := make([]byte, uniformdh.Size+padLen)
- publicKey, err := privateKey.PublicKey.Bytes()
- if err != nil {
- return err
- }
- copy(blob[0:], publicKey)
- if err := csrand.Bytes(blob[uniformdh.Size:]); err != nil {
- return err
- }
- if _, err := conn.Conn.Write(blob); err != nil {
- return err
- }
-
- // Read the public key from the peer.
- rawPeerPublicKey := make([]byte, uniformdh.Size)
- if _, err := io.ReadFull(conn.Conn, rawPeerPublicKey); err != nil {
- return err
- }
- var peerPublicKey uniformdh.PublicKey
- if err := peerPublicKey.SetBytes(rawPeerPublicKey); err != nil {
- return err
- }
-
- // 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).
- sharedSecret, err := uniformdh.Handshake(privateKey, &peerPublicKey)
- if err != nil {
- return err
- }
- if err := conn.kdf(sharedSecret); err != nil {
- return err
- }
-
- return nil
-}
-
-func (conn *obfs3Conn) kdf(sharedSecret []byte) 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.
- initBlock, err := aes.NewCipher(initSecret[:keyLen])
- if err != nil {
- return err
- }
- initStream := cipher.NewCTR(initBlock, initSecret[keyLen:])
-
- respBlock, err := aes.NewCipher(respSecret[:keyLen])
- if err != nil {
- return err
- }
- respStream := cipher.NewCTR(respBlock, respSecret[keyLen:])
-
- if conn.isInitiator {
- conn.tx = &cipher.StreamWriter{S: initStream, W: conn.Conn}
- conn.rx = &cipher.StreamReader{S: respStream, R: conn.rxBuf}
- conn.txMagic = initMagic
- conn.rxMagic = respMagic
- } else {
- conn.tx = &cipher.StreamWriter{S: respStream, W: conn.Conn}
- conn.rx = &cipher.StreamReader{S: initStream, R: conn.rxBuf}
- conn.txMagic = respMagic
- conn.rxMagic = initMagic
- }
-
- return nil
-}
-
-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)
diff --git a/transports/obfs4/framing/framing.go b/transports/obfs4/framing/framing.go
deleted file mode 100644
index 9eaba69..0000000
--- a/transports/obfs4/framing/framing.go
+++ /dev/null
@@ -1,306 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-//
-// Package framing implements the obfs4 link framing and cryptography.
-//
-// The Encoder/Decoder shared secret format is:
-// uint8_t[32] NaCl secretbox key
-// uint8_t[16] NaCl Nonce prefix
-// uint8_t[16] SipHash-2-4 key (used to obfsucate length)
-// uint8_t[8] SipHash-2-4 IV
-//
-// The frame format is:
-// uint16_t length (obfsucated, big endian)
-// NaCl secretbox (Poly1305/XSalsa20) containing:
-// uint8_t[16] tag (Part of the secretbox construct)
-// uint8_t[] payload
-//
-// The length field is length of the NaCl secretbox XORed with the truncated
-// SipHash-2-4 digest ran in OFB mode.
-//
-// Initialize K, IV[0] with values from the shared secret.
-// On each packet, IV[n] = H(K, IV[n - 1])
-// mask[n] = IV[n][0:2]
-// obfsLen = length ^ mask[n]
-//
-// The NaCl secretbox (Poly1305/XSalsa20) nonce format is:
-// uint8_t[24] prefix (Fixed)
-// uint64_t counter (Big endian)
-//
-// The counter is initialized to 1, and is incremented on each frame. Since
-// the protocol is designed to be used over a reliable medium, the nonce is not
-// transmitted over the wire as both sides of the conversation know the prefix
-// and the initial counter value. It is imperative that the counter does not
-// wrap, and sessions MUST terminate before 2^64 frames are sent.
-//
-package framing
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "fmt"
- "io"
-
- "golang.org/x/crypto/nacl/secretbox"
-
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/common/drbg"
-)
-
-const (
- // MaximumSegmentLength is the length of the largest possible segment
- // including overhead.
- MaximumSegmentLength = 1500 - (40 + 12)
-
- // FrameOverhead is the length of the framing overhead.
- FrameOverhead = lengthLength + secretbox.Overhead
-
- // MaximumFramePayloadLength is the length of the maximum allowed payload
- // per frame.
- MaximumFramePayloadLength = MaximumSegmentLength - FrameOverhead
-
- // KeyLength is the length of the Encoder/Decoder secret key.
- KeyLength = keyLength + noncePrefixLength + drbg.SeedLength
-
- maxFrameLength = MaximumSegmentLength - lengthLength
- minFrameLength = FrameOverhead - lengthLength
-
- keyLength = 32
-
- noncePrefixLength = 16
- nonceCounterLength = 8
- nonceLength = noncePrefixLength + nonceCounterLength
-
- lengthLength = 2
-)
-
-// Error returned when Decoder.Decode() requires more data to continue.
-var ErrAgain = errors.New("framing: More data needed to decode")
-
-// Error returned when Decoder.Decode() failes to authenticate a frame.
-var ErrTagMismatch = errors.New("framing: Poly1305 tag mismatch")
-
-// Error returned when the NaCl secretbox nonce's counter wraps (FATAL).
-var ErrNonceCounterWrapped = errors.New("framing: Nonce counter wrapped")
-
-// InvalidPayloadLengthError is the error returned when Encoder.Encode()
-// rejects the payload length.
-type InvalidPayloadLengthError int
-
-func (e InvalidPayloadLengthError) Error() string {
- return fmt.Sprintf("framing: Invalid payload length: %d", int(e))
-}
-
-type boxNonce struct {
- prefix [noncePrefixLength]byte
- counter uint64
-}
-
-func (nonce *boxNonce) init(prefix []byte) {
- if noncePrefixLength != len(prefix) {
- panic(fmt.Sprintf("BUG: Nonce prefix length invalid: %d", len(prefix)))
- }
-
- copy(nonce.prefix[:], prefix)
- nonce.counter = 1
-}
-
-func (nonce boxNonce) bytes(out *[nonceLength]byte) error {
- // The security guarantee of Poly1305 is broken if a nonce is ever reused
- // for a given key. Detect this by checking for counter wraparound since
- // we start each counter at 1. If it ever happens that more than 2^64 - 1
- // frames are transmitted over a given connection, support for rekeying
- // will be neccecary, but that's unlikely to happen.
- if nonce.counter == 0 {
- return ErrNonceCounterWrapped
- }
-
- copy(out[:], nonce.prefix[:])
- binary.BigEndian.PutUint64(out[noncePrefixLength:], nonce.counter)
-
- return nil
-}
-
-// Encoder is a frame encoder instance.
-type Encoder struct {
- key [keyLength]byte
- nonce boxNonce
- drbg *drbg.HashDrbg
-}
-
-// NewEncoder creates a new Encoder instance. It must be supplied a slice
-// containing exactly KeyLength bytes of keying material.
-func NewEncoder(key []byte) *Encoder {
- if len(key) != KeyLength {
- panic(fmt.Sprintf("BUG: Invalid encoder key length: %d", len(key)))
- }
-
- encoder := new(Encoder)
- copy(encoder.key[:], key[0:keyLength])
- encoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
- seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
- if err != nil {
- panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
- }
- encoder.drbg, _ = drbg.NewHashDrbg(seed)
-
- return encoder
-}
-
-// Encode encodes a single frame worth of payload and returns the encoded
-// length. InvalidPayloadLengthError is recoverable, all other errors MUST be
-// treated as fatal and the session aborted.
-func (encoder *Encoder) Encode(frame, payload []byte) (n int, err error) {
- payloadLen := len(payload)
- if MaximumFramePayloadLength < payloadLen {
- return 0, InvalidPayloadLengthError(payloadLen)
- }
- if len(frame) < payloadLen+FrameOverhead {
- return 0, io.ErrShortBuffer
- }
-
- // Generate a new nonce.
- var nonce [nonceLength]byte
- if err = encoder.nonce.bytes(&nonce); err != nil {
- return 0, err
- }
- encoder.nonce.counter++
-
- // Encrypt and MAC payload.
- box := secretbox.Seal(frame[:lengthLength], payload, &nonce, &encoder.key)
-
- // Obfuscate the length.
- length := uint16(len(box) - lengthLength)
- lengthMask := encoder.drbg.NextBlock()
- length ^= binary.BigEndian.Uint16(lengthMask)
- binary.BigEndian.PutUint16(frame[:2], length)
-
- // Return the frame.
- return len(box), nil
-}
-
-// Decoder is a frame decoder instance.
-type Decoder struct {
- key [keyLength]byte
- nonce boxNonce
- drbg *drbg.HashDrbg
-
- nextNonce [nonceLength]byte
- nextLength uint16
- nextLengthInvalid bool
-}
-
-// NewDecoder creates a new Decoder instance. It must be supplied a slice
-// containing exactly KeyLength bytes of keying material.
-func NewDecoder(key []byte) *Decoder {
- if len(key) != KeyLength {
- panic(fmt.Sprintf("BUG: Invalid decoder key length: %d", len(key)))
- }
-
- decoder := new(Decoder)
- copy(decoder.key[:], key[0:keyLength])
- decoder.nonce.init(key[keyLength : keyLength+noncePrefixLength])
- seed, err := drbg.SeedFromBytes(key[keyLength+noncePrefixLength:])
- if err != nil {
- panic(fmt.Sprintf("BUG: Failed to initialize DRBG: %s", err))
- }
- decoder.drbg, _ = drbg.NewHashDrbg(seed)
-
- return decoder
-}
-
-// Decode decodes a stream of data and returns the length if any. ErrAgain is
-// a temporary failure, all other errors MUST be treated as fatal and the
-// session aborted.
-func (decoder *Decoder) Decode(data []byte, frames *bytes.Buffer) (int, error) {
- // A length of 0 indicates that we do not know how big the next frame is
- // going to be.
- if decoder.nextLength == 0 {
- // Attempt to pull out the next frame length.
- if lengthLength > frames.Len() {
- return 0, ErrAgain
- }
-
- // Remove the length field from the buffer.
- var obfsLen [lengthLength]byte
- _, err := io.ReadFull(frames, obfsLen[:])
- if err != nil {
- return 0, err
- }
-
- // Derive the nonce the peer used.
- if err = decoder.nonce.bytes(&decoder.nextNonce); err != nil {
- return 0, err
- }
-
- // Deobfuscate the length field.
- length := binary.BigEndian.Uint16(obfsLen[:])
- lengthMask := decoder.drbg.NextBlock()
- length ^= binary.BigEndian.Uint16(lengthMask)
- if maxFrameLength < length || minFrameLength > length {
- // Per "Plaintext Recovery Attacks Against SSH" by
- // Martin R. Albrecht, Kenneth G. Paterson and Gaven J. Watson,
- // there are a class of attacks againt protocols that use similar
- // sorts of framing schemes.
- //
- // While obfs4 should not allow plaintext recovery (CBC mode is
- // not used), attempt to mitigate out of bound frame length errors
- // by pretending that the length was a random valid range as per
- // the countermeasure suggested by Denis Bider in section 6 of the
- // paper.
-
- decoder.nextLengthInvalid = true
- length = uint16(csrand.IntRange(minFrameLength, maxFrameLength))
- }
- decoder.nextLength = length
- }
-
- if int(decoder.nextLength) > frames.Len() {
- return 0, ErrAgain
- }
-
- // Unseal the frame.
- var box [maxFrameLength]byte
- n, err := io.ReadFull(frames, box[:decoder.nextLength])
- if err != nil {
- return 0, err
- }
- out, ok := secretbox.Open(data[:0], box[:n], &decoder.nextNonce, &decoder.key)
- if !ok || decoder.nextLengthInvalid {
- // When a random length is used (on length error) the tag should always
- // mismatch, but be paranoid.
- return 0, ErrTagMismatch
- }
-
- // Clean up and prepare for the next frame.
- decoder.nextLength = 0
- decoder.nonce.counter++
-
- return len(out), nil
-}
diff --git a/transports/obfs4/framing/framing_test.go b/transports/obfs4/framing/framing_test.go
deleted file mode 100644
index 03e0d3b..0000000
--- a/transports/obfs4/framing/framing_test.go
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-package framing
-
-import (
- "bytes"
- "crypto/rand"
- "testing"
-)
-
-func generateRandomKey() []byte {
- key := make([]byte, KeyLength)
-
- _, err := rand.Read(key)
- if err != nil {
- panic(err)
- }
-
- return key
-}
-
-func newEncoder(t *testing.T) *Encoder {
- // Generate a key to use.
- key := generateRandomKey()
-
- encoder := NewEncoder(key)
- if encoder == nil {
- t.Fatalf("NewEncoder returned nil")
- }
-
- return encoder
-}
-
-// TestNewEncoder tests the Encoder ctor.
-func TestNewEncoder(t *testing.T) {
- encoder := newEncoder(t)
- _ = encoder
-}
-
-// TestEncoder_Encode tests Encoder.Encode.
-func TestEncoder_Encode(t *testing.T) {
- encoder := newEncoder(t)
-
- buf := make([]byte, MaximumFramePayloadLength)
- _, _ = rand.Read(buf) // YOLO
- for i := 0; i <= MaximumFramePayloadLength; i++ {
- var frame [MaximumSegmentLength]byte
- n, err := encoder.Encode(frame[:], buf[0:i])
- if err != nil {
- t.Fatalf("Encoder.encode([%d]byte), failed: %s", i, err)
- }
- if n != i+FrameOverhead {
- t.Fatalf("Unexpected encoded framesize: %d, expecting %d", n, i+
- FrameOverhead)
- }
- }
-}
-
-// TestEncoder_Encode_Oversize tests oversized frame rejection.
-func TestEncoder_Encode_Oversize(t *testing.T) {
- encoder := newEncoder(t)
-
- var frame [MaximumSegmentLength]byte
- var buf [MaximumFramePayloadLength + 1]byte
- _, _ = rand.Read(buf[:]) // YOLO
- _, err := encoder.Encode(frame[:], buf[:])
- if _, ok := err.(InvalidPayloadLengthError); !ok {
- t.Error("Encoder.encode() returned unexpected error:", err)
- }
-}
-
-// TestNewDecoder tests the Decoder ctor.
-func TestNewDecoder(t *testing.T) {
- key := generateRandomKey()
- decoder := NewDecoder(key)
- if decoder == nil {
- t.Fatalf("NewDecoder returned nil")
- }
-}
-
-// TestDecoder_Decode tests Decoder.Decode.
-func TestDecoder_Decode(t *testing.T) {
- key := generateRandomKey()
-
- encoder := NewEncoder(key)
- decoder := NewDecoder(key)
-
- var buf [MaximumFramePayloadLength]byte
- _, _ = rand.Read(buf[:]) // YOLO
- for i := 0; i <= MaximumFramePayloadLength; i++ {
- var frame [MaximumSegmentLength]byte
- encLen, err := encoder.Encode(frame[:], buf[0:i])
- if err != nil {
- t.Fatalf("Encoder.encode([%d]byte), failed: %s", i, err)
- }
- if encLen != i+FrameOverhead {
- t.Fatalf("Unexpected encoded framesize: %d, expecting %d", encLen,
- i+FrameOverhead)
- }
-
- var decoded [MaximumFramePayloadLength]byte
-
- decLen, err := decoder.Decode(decoded[:], bytes.NewBuffer(frame[:encLen]))
- if err != nil {
- t.Fatalf("Decoder.decode([%d]byte), failed: %s", i, err)
- }
- if decLen != i {
- t.Fatalf("Unexpected decoded framesize: %d, expecting %d",
- decLen, i)
- }
-
- if 0 != bytes.Compare(decoded[:decLen], buf[:i]) {
- t.Fatalf("Frame %d does not match encoder input", i)
- }
- }
-}
-
-// BencharkEncoder_Encode benchmarks Encoder.Encode processing 1 MiB
-// of payload.
-func BenchmarkEncoder_Encode(b *testing.B) {
- var chopBuf [MaximumFramePayloadLength]byte
- var frame [MaximumSegmentLength]byte
- payload := make([]byte, 1024*1024)
- encoder := NewEncoder(generateRandomKey())
- b.ResetTimer()
-
- for i := 0; i < b.N; i++ {
- transfered := 0
- buffer := bytes.NewBuffer(payload)
- for 0 < buffer.Len() {
- n, err := buffer.Read(chopBuf[:])
- if err != nil {
- b.Fatal("buffer.Read() failed:", err)
- }
-
- n, err = encoder.Encode(frame[:], chopBuf[:n])
- transfered += n - FrameOverhead
- }
- if transfered != len(payload) {
- b.Fatalf("Transfered length mismatch: %d != %d", transfered,
- len(payload))
- }
- }
-}
diff --git a/transports/obfs4/handshake_ntor.go b/transports/obfs4/handshake_ntor.go
deleted file mode 100644
index ed7c114..0000000
--- a/transports/obfs4/handshake_ntor.go
+++ /dev/null
@@ -1,425 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-package obfs4
-
-import (
- "bytes"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/hex"
- "errors"
- "fmt"
- "hash"
- "strconv"
- "time"
-
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/common/ntor"
- "github.com/OperatorFoundation/obfs4/common/replayfilter"
- "github.com/OperatorFoundation/obfs4/transports/obfs4/framing"
-)
-
-const (
- maxHandshakeLength = 8192
-
- clientMinPadLength = (serverMinHandshakeLength + inlineSeedFrameLength) -
- clientMinHandshakeLength
- clientMaxPadLength = maxHandshakeLength - clientMinHandshakeLength
- clientMinHandshakeLength = ntor.RepresentativeLength + markLength + macLength
-
- serverMinPadLength = 0
- serverMaxPadLength = maxHandshakeLength - (serverMinHandshakeLength +
- inlineSeedFrameLength)
- serverMinHandshakeLength = ntor.RepresentativeLength + ntor.AuthLength +
- markLength + macLength
-
- markLength = sha256.Size / 2
- macLength = sha256.Size / 2
-
- inlineSeedFrameLength = framing.FrameOverhead + packetOverhead + seedPacketPayloadLength
-)
-
-// ErrMarkNotFoundYet is the error returned when the obfs4 handshake is
-// incomplete and requires more data to continue. This error is non-fatal and
-// is the equivalent to EAGAIN/EWOULDBLOCK.
-var ErrMarkNotFoundYet = errors.New("handshake: M_[C,S] not found yet")
-
-// ErrInvalidHandshake is the error returned when the obfs4 handshake fails due
-// to the peer not sending the correct mark. This error is fatal and the
-// connection MUST be dropped.
-var ErrInvalidHandshake = errors.New("handshake: Failed to find M_[C,S]")
-
-// ErrReplayedHandshake is the error returned when the obfs4 handshake fails
-// due it being replayed. This error is fatal and the connection MUST be
-// dropped.
-var ErrReplayedHandshake = errors.New("handshake: Replay detected")
-
-// ErrNtorFailed is the error returned when the ntor handshake fails. This
-// error is fatal and the connection MUST be dropped.
-var ErrNtorFailed = errors.New("handshake: ntor handshake failure")
-
-// InvalidMacError is the error returned when the handshake MACs do not match.
-// This error is fatal and the connection MUST be dropped.
-type InvalidMacError struct {
- Derived []byte
- Received []byte
-}
-
-func (e *InvalidMacError) Error() string {
- return fmt.Sprintf("handshake: MAC mismatch: Dervied: %s Received: %s.",
- hex.EncodeToString(e.Derived), hex.EncodeToString(e.Received))
-}
-
-// InvalidAuthError is the error returned when the ntor AUTH tags do not match.
-// This error is fatal and the connection MUST be dropped.
-type InvalidAuthError struct {
- Derived *ntor.Auth
- Received *ntor.Auth
-}
-
-func (e *InvalidAuthError) Error() string {
- return fmt.Sprintf("handshake: ntor AUTH mismatch: Derived: %s Received:%s.",
- hex.EncodeToString(e.Derived.Bytes()[:]),
- hex.EncodeToString(e.Received.Bytes()[:]))
-}
-
-type clientHandshake struct {
- keypair *ntor.Keypair
- nodeID *ntor.NodeID
- serverIdentity *ntor.PublicKey
- epochHour []byte
-
- padLen int
- mac hash.Hash
-
- serverRepresentative *ntor.Representative
- serverAuth *ntor.Auth
- serverMark []byte
-}
-
-func newClientHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.PublicKey, sessionKey *ntor.Keypair) *clientHandshake {
- hs := new(clientHandshake)
- hs.keypair = sessionKey
- hs.nodeID = nodeID
- hs.serverIdentity = serverIdentity
- hs.padLen = csrand.IntRange(clientMinPadLength, clientMaxPadLength)
- hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Bytes()[:], hs.nodeID.Bytes()[:]...))
-
- return hs
-}
-
-func (hs *clientHandshake) generateHandshake() ([]byte, error) {
- var buf bytes.Buffer
-
- hs.mac.Reset()
- hs.mac.Write(hs.keypair.Representative().Bytes()[:])
- mark := hs.mac.Sum(nil)[:markLength]
-
- // The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E) where:
- // * X is the client's ephemeral Curve25519 public key representative.
- // * P_C is [clientMinPadLength,clientMaxPadLength] bytes of random padding.
- // * M_C is HMAC-SHA256-128(serverIdentity | NodeID, X)
- // * MAC is HMAC-SHA256-128(serverIdentity | NodeID, X .... E)
- // * E is the string representation of the number of hours since the UNIX
- // epoch.
-
- // Generate the padding
- pad, err := makePad(hs.padLen)
- if err != nil {
- return nil, err
- }
-
- // Write X, P_C, M_C.
- buf.Write(hs.keypair.Representative().Bytes()[:])
- buf.Write(pad)
- buf.Write(mark)
-
- // Calculate and write the MAC.
- hs.mac.Reset()
- hs.mac.Write(buf.Bytes())
- hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10))
- hs.mac.Write(hs.epochHour)
- buf.Write(hs.mac.Sum(nil)[:macLength])
-
- return buf.Bytes(), nil
-}
-
-func (hs *clientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) {
- // No point in examining the data unless the miminum plausible response has
- // been received.
- if serverMinHandshakeLength > len(resp) {
- return 0, nil, ErrMarkNotFoundYet
- }
-
- if hs.serverRepresentative == nil || hs.serverAuth == nil {
- // Pull out the representative/AUTH. (XXX: Add ctors to ntor)
- hs.serverRepresentative = new(ntor.Representative)
- copy(hs.serverRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength])
- hs.serverAuth = new(ntor.Auth)
- copy(hs.serverAuth.Bytes()[:], resp[ntor.RepresentativeLength:])
-
- // Derive the mark.
- hs.mac.Reset()
- hs.mac.Write(hs.serverRepresentative.Bytes()[:])
- hs.serverMark = hs.mac.Sum(nil)[:markLength]
- }
-
- // Attempt to find the mark + MAC.
- pos := findMarkMac(hs.serverMark, resp, ntor.RepresentativeLength+ntor.AuthLength+serverMinPadLength,
- maxHandshakeLength, false)
- if pos == -1 {
- if len(resp) >= maxHandshakeLength {
- return 0, nil, ErrInvalidHandshake
- }
- return 0, nil, ErrMarkNotFoundYet
- }
-
- // Validate the MAC.
- hs.mac.Reset()
- hs.mac.Write(resp[:pos+markLength])
- hs.mac.Write(hs.epochHour)
- macCmp := hs.mac.Sum(nil)[:macLength]
- macRx := resp[pos+markLength : pos+markLength+macLength]
- if !hmac.Equal(macCmp, macRx) {
- return 0, nil, &InvalidMacError{macCmp, macRx}
- }
-
- // Complete the handshake.
- serverPublic := hs.serverRepresentative.ToPublic()
- ok, seed, auth := ntor.ClientHandshake(hs.keypair, serverPublic,
- hs.serverIdentity, hs.nodeID)
- if !ok {
- return 0, nil, ErrNtorFailed
- }
- if !ntor.CompareAuth(auth, hs.serverAuth.Bytes()[:]) {
- return 0, nil, &InvalidAuthError{auth, hs.serverAuth}
- }
-
- return pos + markLength + macLength, seed.Bytes()[:], nil
-}
-
-type serverHandshake struct {
- keypair *ntor.Keypair
- nodeID *ntor.NodeID
- serverIdentity *ntor.Keypair
- epochHour []byte
- serverAuth *ntor.Auth
-
- padLen int
- mac hash.Hash
-
- clientRepresentative *ntor.Representative
- clientMark []byte
-}
-
-func newServerHandshake(nodeID *ntor.NodeID, serverIdentity *ntor.Keypair, sessionKey *ntor.Keypair) *serverHandshake {
- hs := new(serverHandshake)
- hs.keypair = sessionKey
- hs.nodeID = nodeID
- hs.serverIdentity = serverIdentity
- hs.padLen = csrand.IntRange(serverMinPadLength, serverMaxPadLength)
- hs.mac = hmac.New(sha256.New, append(hs.serverIdentity.Public().Bytes()[:], hs.nodeID.Bytes()[:]...))
-
- return hs
-}
-
-func (hs *serverHandshake) parseClientHandshake(filter *replayfilter.ReplayFilter, resp []byte) ([]byte, error) {
- // No point in examining the data unless the miminum plausible response has
- // been received.
- if clientMinHandshakeLength > len(resp) {
- return nil, ErrMarkNotFoundYet
- }
-
- if hs.clientRepresentative == nil {
- // Pull out the representative/AUTH. (XXX: Add ctors to ntor)
- hs.clientRepresentative = new(ntor.Representative)
- copy(hs.clientRepresentative.Bytes()[:], resp[0:ntor.RepresentativeLength])
-
- // Derive the mark.
- hs.mac.Reset()
- hs.mac.Write(hs.clientRepresentative.Bytes()[:])
- hs.clientMark = hs.mac.Sum(nil)[:markLength]
- }
-
- // Attempt to find the mark + MAC.
- pos := findMarkMac(hs.clientMark, resp, ntor.RepresentativeLength+clientMinPadLength,
- maxHandshakeLength, true)
- if pos == -1 {
- if len(resp) >= maxHandshakeLength {
- return nil, ErrInvalidHandshake
- }
- return nil, ErrMarkNotFoundYet
- }
-
- // Validate the MAC.
- macFound := false
- for _, off := range []int64{0, -1, 1} {
- // Allow epoch to be off by up to a hour in either direction.
- epochHour := []byte(strconv.FormatInt(getEpochHour()+int64(off), 10))
- hs.mac.Reset()
- hs.mac.Write(resp[:pos+markLength])
- hs.mac.Write(epochHour)
- macCmp := hs.mac.Sum(nil)[:macLength]
- macRx := resp[pos+markLength : pos+markLength+macLength]
- if hmac.Equal(macCmp, macRx) {
- // Ensure that this handshake has not been seen previously.
- if filter.TestAndSet(time.Now(), macRx) {
- // The client either happened to generate exactly the same
- // session key and padding, or someone is replaying a previous
- // handshake. In either case, fuck them.
- return nil, ErrReplayedHandshake
- }
-
- macFound = true
- hs.epochHour = epochHour
-
- // We could break out here, but in the name of reducing timing
- // variation, evaluate all 3 MACs.
- }
- }
- if !macFound {
- // This probably should be an InvalidMacError, but conveying the 3 MACS
- // that would be accepted is annoying so just return a generic fatal
- // failure.
- return nil, ErrInvalidHandshake
- }
-
- // Client should never sent trailing garbage.
- if len(resp) != pos+markLength+macLength {
- return nil, ErrInvalidHandshake
- }
-
- clientPublic := hs.clientRepresentative.ToPublic()
- ok, seed, auth := ntor.ServerHandshake(clientPublic, hs.keypair,
- hs.serverIdentity, hs.nodeID)
- if !ok {
- return nil, ErrNtorFailed
- }
- hs.serverAuth = auth
-
- return seed.Bytes()[:], nil
-}
-
-func (hs *serverHandshake) generateHandshake() ([]byte, error) {
- var buf bytes.Buffer
-
- hs.mac.Reset()
- hs.mac.Write(hs.keypair.Representative().Bytes()[:])
- mark := hs.mac.Sum(nil)[:markLength]
-
- // The server handshake is Y | AUTH | P_S | M_S | MAC(Y | AUTH | P_S | M_S | E) where:
- // * Y is the server's ephemeral Curve25519 public key representative.
- // * AUTH is the ntor handshake AUTH value.
- // * P_S is [serverMinPadLength,serverMaxPadLength] bytes of random padding.
- // * M_S is HMAC-SHA256-128(serverIdentity | NodeID, Y)
- // * MAC is HMAC-SHA256-128(serverIdentity | NodeID, Y .... E)
- // * E is the string representation of the number of hours since the UNIX
- // epoch.
-
- // Generate the padding
- pad, err := makePad(hs.padLen)
- if err != nil {
- return nil, err
- }
-
- // Write Y, AUTH, P_S, M_S.
- buf.Write(hs.keypair.Representative().Bytes()[:])
- buf.Write(hs.serverAuth.Bytes()[:])
- buf.Write(pad)
- buf.Write(mark)
-
- // Calculate and write the MAC.
- hs.mac.Reset()
- hs.mac.Write(buf.Bytes())
- hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10))
- hs.mac.Write(hs.epochHour)
- buf.Write(hs.mac.Sum(nil)[:macLength])
-
- return buf.Bytes(), nil
-}
-
-// getEpochHour returns the number of hours since the UNIX epoch.
-func getEpochHour() int64 {
- return time.Now().Unix() / 3600
-}
-
-func findMarkMac(mark, buf []byte, startPos, maxPos int, fromTail bool) (pos int) {
- if len(mark) != markLength {
- panic(fmt.Sprintf("BUG: Invalid mark length: %d", len(mark)))
- }
-
- endPos := len(buf)
- if startPos > len(buf) {
- return -1
- }
- if endPos > maxPos {
- endPos = maxPos
- }
- if endPos-startPos < markLength+macLength {
- return -1
- }
-
- if fromTail {
- // The server can optimize the search process by only examining the
- // tail of the buffer. The client can't send valid data past M_C |
- // MAC_C as it does not have the server's public key yet.
- pos = endPos - (markLength + macLength)
- if !hmac.Equal(buf[pos:pos+markLength], mark) {
- return -1
- }
-
- return
- }
-
- // The client has to actually do a substring search since the server can
- // and will send payload trailing the response.
- //
- // XXX: bytes.Index() uses a naive search, which kind of sucks.
- pos = bytes.Index(buf[startPos:endPos], mark)
- if pos == -1 {
- return -1
- }
-
- // Ensure that there is enough trailing data for the MAC.
- if startPos+pos+markLength+macLength > endPos {
- return -1
- }
-
- // Return the index relative to the start of the slice.
- pos += startPos
- return
-}
-
-func makePad(padLen int) ([]byte, error) {
- pad := make([]byte, padLen)
- if err := csrand.Bytes(pad); err != nil {
- return nil, err
- }
-
- return pad, nil
-}
diff --git a/transports/obfs4/handshake_ntor_test.go b/transports/obfs4/handshake_ntor_test.go
deleted file mode 100644
index 04afcef..0000000
--- a/transports/obfs4/handshake_ntor_test.go
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-package obfs4
-
-import (
- "bytes"
- "testing"
-
- "github.com/OperatorFoundation/obfs4/common/ntor"
- "github.com/OperatorFoundation/obfs4/common/replayfilter"
-)
-
-func TestHandshakeNtorClient(t *testing.T) {
- // Generate the server node id and id keypair, and ephemeral session keys.
- nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"))
- idKeypair, _ := ntor.NewKeypair(false)
- serverFilter, _ := replayfilter.New(replayTTL)
- clientKeypair, err := ntor.NewKeypair(true)
- if err != nil {
- t.Fatalf("client: ntor.NewKeypair failed: %s", err)
- }
- serverKeypair, err := ntor.NewKeypair(true)
- if err != nil {
- t.Fatalf("server: ntor.NewKeypair failed: %s", err)
- }
-
- // Test client handshake padding.
- for l := clientMinPadLength; l <= clientMaxPadLength; l++ {
- // Generate the client state and override the pad length.
- clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair)
- clientHs.padLen = l
-
- // Generate what the client will send to the server.
- clientBlob, err := clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("[%d:0] clientHandshake.generateHandshake() failed: %s", l, err)
- }
- if len(clientBlob) > maxHandshakeLength {
- t.Fatalf("[%d:0] Generated client body is oversized: %d", l, len(clientBlob))
- }
- if len(clientBlob) < clientMinHandshakeLength {
- t.Fatalf("[%d:0] Generated client body is undersized: %d", l, len(clientBlob))
- }
- if len(clientBlob) != clientMinHandshakeLength+l {
- t.Fatalf("[%d:0] Generated client body incorrect size: %d", l, len(clientBlob))
- }
-
- // Generate the server state and override the pad length.
- serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair)
- serverHs.padLen = serverMinPadLength
-
- // Parse the client handshake message.
- serverSeed, err := serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err != nil {
- t.Fatalf("[%d:0] serverHandshake.parseClientHandshake() failed: %s", l, err)
- }
-
- // Genrate what the server will send to the client.
- serverBlob, err := serverHs.generateHandshake()
- if err != nil {
- t.Fatalf("[%d:0]: serverHandshake.generateHandshake() failed: %s", l, err)
- }
-
- // Parse the server handshake message.
- clientHs.serverRepresentative = nil
- n, clientSeed, err := clientHs.parseServerHandshake(serverBlob)
- if err != nil {
- t.Fatalf("[%d:0] clientHandshake.parseServerHandshake() failed: %s", l, err)
- }
- if n != len(serverBlob) {
- t.Fatalf("[%d:0] clientHandshake.parseServerHandshake() has bytes remaining: %d", l, n)
- }
-
- // Ensure the derived shared secret is the same.
- if 0 != bytes.Compare(clientSeed, serverSeed) {
- t.Fatalf("[%d:0] client/server seed mismatch", l)
- }
- }
-
- // Test oversized client padding.
- clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair)
- if err != nil {
- t.Fatalf("newClientHandshake failed: %s", err)
- }
- clientHs.padLen = clientMaxPadLength + 1
- clientBlob, err := clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("clientHandshake.generateHandshake() (forced oversize) failed: %s", err)
- }
- serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair)
- _, err = serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err == nil {
- t.Fatalf("serverHandshake.parseClientHandshake() succeded (oversized)")
- }
-
- // Test undersized client padding.
- clientHs.padLen = clientMinPadLength - 1
- clientBlob, err = clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("clientHandshake.generateHandshake() (forced undersize) failed: %s", err)
- }
- serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair)
- _, err = serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err == nil {
- t.Fatalf("serverHandshake.parseClientHandshake() succeded (undersized)")
- }
-}
-
-func TestHandshakeNtorServer(t *testing.T) {
- // Generate the server node id and id keypair, and ephemeral session keys.
- nodeID, _ := ntor.NewNodeID([]byte("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13"))
- idKeypair, _ := ntor.NewKeypair(false)
- serverFilter, _ := replayfilter.New(replayTTL)
- clientKeypair, err := ntor.NewKeypair(true)
- if err != nil {
- t.Fatalf("client: ntor.NewKeypair failed: %s", err)
- }
- serverKeypair, err := ntor.NewKeypair(true)
- if err != nil {
- t.Fatalf("server: ntor.NewKeypair failed: %s", err)
- }
-
- // Test server handshake padding.
- for l := serverMinPadLength; l <= serverMaxPadLength+inlineSeedFrameLength; l++ {
- // Generate the client state and override the pad length.
- clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair)
- clientHs.padLen = clientMinPadLength
-
- // Generate what the client will send to the server.
- clientBlob, err := clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("[%d:1] clientHandshake.generateHandshake() failed: %s", l, err)
- }
- if len(clientBlob) > maxHandshakeLength {
- t.Fatalf("[%d:1] Generated client body is oversized: %d", l, len(clientBlob))
- }
-
- // Generate the server state and override the pad length.
- serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair)
- serverHs.padLen = l
-
- // Parse the client handshake message.
- serverSeed, err := serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err != nil {
- t.Fatalf("[%d:1] serverHandshake.parseClientHandshake() failed: %s", l, err)
- }
-
- // Genrate what the server will send to the client.
- serverBlob, err := serverHs.generateHandshake()
- if err != nil {
- t.Fatalf("[%d:1]: serverHandshake.generateHandshake() failed: %s", l, err)
- }
-
- // Parse the server handshake message.
- n, clientSeed, err := clientHs.parseServerHandshake(serverBlob)
- if err != nil {
- t.Fatalf("[%d:1] clientHandshake.parseServerHandshake() failed: %s", l, err)
- }
- if n != len(serverBlob) {
- t.Fatalf("[%d:1] clientHandshake.parseServerHandshake() has bytes remaining: %d", l, n)
- }
-
- // Ensure the derived shared secret is the same.
- if 0 != bytes.Compare(clientSeed, serverSeed) {
- t.Fatalf("[%d:1] client/server seed mismatch", l)
- }
- }
-
- // Test oversized client padding.
- clientHs := newClientHandshake(nodeID, idKeypair.Public(), clientKeypair)
- if err != nil {
- t.Fatalf("newClientHandshake failed: %s", err)
- }
- clientHs.padLen = clientMaxPadLength + 1
- clientBlob, err := clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("clientHandshake.generateHandshake() (forced oversize) failed: %s", err)
- }
- serverHs := newServerHandshake(nodeID, idKeypair, serverKeypair)
- _, err = serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err == nil {
- t.Fatalf("serverHandshake.parseClientHandshake() succeded (oversized)")
- }
-
- // Test undersized client padding.
- clientHs.padLen = clientMinPadLength - 1
- clientBlob, err = clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("clientHandshake.generateHandshake() (forced undersize) failed: %s", err)
- }
- serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair)
- _, err = serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err == nil {
- t.Fatalf("serverHandshake.parseClientHandshake() succeded (undersized)")
- }
-
- // Test oversized server padding.
- //
- // NB: serverMaxPadLength isn't the real maxPadLength that triggers client
- // rejection, because the implementation is written with the asusmption
- // that the PRNG_SEED is also inlined with the response. Thus the client
- // actually accepts longer padding. The server handshake test and this
- // test adjust around that.
- clientHs.padLen = clientMinPadLength
- clientBlob, err = clientHs.generateHandshake()
- if err != nil {
- t.Fatalf("clientHandshake.generateHandshake() failed: %s", err)
- }
- serverHs = newServerHandshake(nodeID, idKeypair, serverKeypair)
- serverHs.padLen = serverMaxPadLength + inlineSeedFrameLength + 1
- _, err = serverHs.parseClientHandshake(serverFilter, clientBlob)
- if err != nil {
- t.Fatalf("serverHandshake.parseClientHandshake() failed: %s", err)
- }
- serverBlob, err := serverHs.generateHandshake()
- if err != nil {
- t.Fatalf("serverHandshake.generateHandshake() (forced oversize) failed: %s", err)
- }
- _, _, err = clientHs.parseServerHandshake(serverBlob)
- if err == nil {
- t.Fatalf("clientHandshake.parseServerHandshake() succeded (oversized)")
- }
-}
diff --git a/transports/obfs4/obfs4.go b/transports/obfs4/obfs4.go
deleted file mode 100644
index 269e1c6..0000000
--- a/transports/obfs4/obfs4.go
+++ /dev/null
@@ -1,646 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-// Package obfs4 provides an implementation of the Tor Project's obfs4
-// obfuscation protocol.
-package obfs4
-
-import (
- "bytes"
- "crypto/sha256"
- "flag"
- "fmt"
- "math/rand"
- "net"
- "strconv"
- "syscall"
- "time"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
- "github.com/OperatorFoundation/obfs4/common/drbg"
- "github.com/OperatorFoundation/obfs4/common/ntor"
- "github.com/OperatorFoundation/obfs4/common/probdist"
- "github.com/OperatorFoundation/obfs4/common/replayfilter"
- "github.com/OperatorFoundation/obfs4/transports/base"
- "github.com/OperatorFoundation/obfs4/transports/obfs4/framing"
-)
-
-const (
- transportName = "obfs4"
-
- nodeIDArg = "node-id"
- publicKeyArg = "public-key"
- privateKeyArg = "private-key"
- seedArg = "drbg-seed"
- iatArg = "iat-mode"
- certArg = "cert"
-
- biasCmdArg = "obfs4-distBias"
-
- seedLength = drbg.SeedLength
- headerLength = framing.FrameOverhead + packetOverhead
- clientHandshakeTimeout = time.Duration(60) * time.Second
- serverHandshakeTimeout = time.Duration(30) * time.Second
- replayTTL = time.Duration(3) * time.Hour
-
- maxIATDelay = 100
- maxCloseDelayBytes = maxHandshakeLength
- maxCloseDelay = 60
-)
-
-const (
- iatNone = iota
- iatEnabled
- iatParanoid
-)
-
-// biasedDist controls if the probability table will be ScrambleSuit style or
-// uniformly distributed.
-var biasedDist bool
-
-type obfs4ClientArgs struct {
- nodeID *ntor.NodeID
- publicKey *ntor.PublicKey
- sessionKey *ntor.Keypair
- iatMode int
-}
-
-// Transport is the obfs4 implementation of the base.Transport interface.
-type Transport struct{}
-
-// Name returns the name of the obfs4 transport protocol.
-func (t *Transport) Name() string {
- return transportName
-}
-
-// ClientFactory returns a new obfs4ClientFactory instance.
-func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
- cf := &obfs4ClientFactory{transport: t}
- return cf, nil
-}
-
-// ServerFactory returns a new obfs4ServerFactory instance.
-func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
- st, err := serverStateFromArgs(stateDir, args)
- if err != nil {
- return nil, err
- }
-
- var iatSeed *drbg.Seed
- if st.iatMode != iatNone {
- iatSeedSrc := sha256.Sum256(st.drbgSeed.Bytes()[:])
- var err error
- iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:])
- if err != nil {
- return nil, err
- }
- }
-
- // Store the arguments that should appear in our descriptor for the clients.
- ptArgs := pt.Args{}
- ptArgs.Add(certArg, st.cert.String())
- ptArgs.Add(iatArg, strconv.Itoa(st.iatMode))
-
- // Initialize the replay filter.
- filter, err := replayfilter.New(replayTTL)
- if err != nil {
- return nil, err
- }
-
- // Initialize the close thresholds for failed connections.
- drbg, err := drbg.NewHashDrbg(st.drbgSeed)
- if err != nil {
- return nil, err
- }
- rng := rand.New(drbg)
-
- sf := &obfs4ServerFactory{t, &ptArgs, st.nodeID, st.identityKey, st.drbgSeed, iatSeed, st.iatMode, filter, rng.Intn(maxCloseDelayBytes), rng.Intn(maxCloseDelay)}
- return sf, nil
-}
-
-type obfs4ClientFactory struct {
- transport base.Transport
-}
-
-func (cf *obfs4ClientFactory) Transport() base.Transport {
- return cf.transport
-}
-
-func (cf *obfs4ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
- var nodeID *ntor.NodeID
- var publicKey *ntor.PublicKey
-
- // The "new" (version >= 0.0.3) bridge lines use a unified "cert" argument
- // for the Node ID and Public Key.
- certStr, ok := args.Get(certArg)
- if ok {
- cert, err := serverCertFromString(certStr)
- if err != nil {
- return nil, err
- }
- nodeID, publicKey = cert.unpack()
- } else {
- // The "old" style (version <= 0.0.2) bridge lines use separate Node ID
- // and Public Key arguments in Base16 encoding and are a UX disaster.
- nodeIDStr, ok := args.Get(nodeIDArg)
- if !ok {
- return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
- }
- var err error
- if nodeID, err = ntor.NodeIDFromHex(nodeIDStr); err != nil {
- return nil, err
- }
-
- publicKeyStr, ok := args.Get(publicKeyArg)
- if !ok {
- return nil, fmt.Errorf("missing argument '%s'", publicKeyArg)
- }
- if publicKey, err = ntor.PublicKeyFromHex(publicKeyStr); err != nil {
- return nil, err
- }
- }
-
- // IAT config is common across the two bridge line formats.
- iatStr, ok := args.Get(iatArg)
- if !ok {
- return nil, fmt.Errorf("missing argument '%s'", iatArg)
- }
- iatMode, err := strconv.Atoi(iatStr)
- if err != nil || iatMode < iatNone || iatMode > iatParanoid {
- return nil, fmt.Errorf("invalid iat-mode '%d'", iatMode)
- }
-
- // Generate the session key pair before connectiong to hide the Elligator2
- // rejection sampling from network observers.
- sessionKey, err := ntor.NewKeypair(true)
- if err != nil {
- return nil, err
- }
-
- return &obfs4ClientArgs{nodeID, publicKey, sessionKey, iatMode}, nil
-}
-
-func (cf *obfs4ClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
- // Validate args before bothering to open connection.
- ca, ok := args.(*obfs4ClientArgs)
- if !ok {
- return nil, fmt.Errorf("invalid argument type for args")
- }
- conn, err := dialFn(network, addr)
- if err != nil {
- return nil, err
- }
- dialConn := conn
- if conn, err = newObfs4ClientConn(conn, ca); err != nil {
- dialConn.Close()
- return nil, err
- }
- return conn, nil
-}
-
-type obfs4ServerFactory struct {
- transport base.Transport
- args *pt.Args
-
- nodeID *ntor.NodeID
- identityKey *ntor.Keypair
- lenSeed *drbg.Seed
- iatSeed *drbg.Seed
- iatMode int
- replayFilter *replayfilter.ReplayFilter
-
- closeDelayBytes int
- closeDelay int
-}
-
-func (sf *obfs4ServerFactory) Transport() base.Transport {
- return sf.transport
-}
-
-func (sf *obfs4ServerFactory) Args() *pt.Args {
- return sf.args
-}
-
-func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
- // Not much point in having a separate newObfs4ServerConn routine when
- // wrapping requires using values from the factory instance.
-
- // Generate the session keypair *before* consuming data from the peer, to
- // attempt to mask the rejection sampling due to use of Elligator2. This
- // might be futile, but the timing differential isn't very large on modern
- // hardware, and there are far easier statistical attacks that can be
- // mounted as a distinguisher.
- sessionKey, err := ntor.NewKeypair(true)
- if err != nil {
- return nil, err
- }
-
- lenDist := probdist.New(sf.lenSeed, 0, framing.MaximumSegmentLength, biasedDist)
- var iatDist *probdist.WeightedDist
- if sf.iatSeed != nil {
- iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
- }
-
- c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
-
- startTime := time.Now()
-
- if err = c.serverHandshake(sf, sessionKey); err != nil {
- c.closeAfterDelay(sf, startTime)
- return nil, err
- }
-
- return c, nil
-}
-
-type obfs4Conn struct {
- net.Conn
-
- isServer bool
-
- lenDist *probdist.WeightedDist
- iatDist *probdist.WeightedDist
- iatMode int
-
- receiveBuffer *bytes.Buffer
- receiveDecodedBuffer *bytes.Buffer
-
- encoder *framing.Encoder
- decoder *framing.Decoder
-}
-
-func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
- // Generate the initial protocol polymorphism distribution(s).
- var seed *drbg.Seed
- if seed, err = drbg.NewSeed(); err != nil {
- return
- }
- lenDist := probdist.New(seed, 0, framing.MaximumSegmentLength, biasedDist)
- var iatDist *probdist.WeightedDist
- if args.iatMode != iatNone {
- var iatSeed *drbg.Seed
- iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
- if iatSeed, err = drbg.SeedFromBytes(iatSeedSrc[:]); err != nil {
- return
- }
- iatDist = probdist.New(iatSeed, 0, maxIATDelay, biasedDist)
- }
-
- // Allocate the client structure.
- c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), nil, nil}
-
- // Start the handshake timeout.
- deadline := time.Now().Add(clientHandshakeTimeout)
- if err = conn.SetDeadline(deadline); err != nil {
- return nil, err
- }
-
- if err = c.clientHandshake(args.nodeID, args.publicKey, args.sessionKey); err != nil {
- return nil, err
- }
-
- // Stop the handshake timeout.
- if err = conn.SetDeadline(time.Time{}); err != nil {
- return nil, err
- }
-
- return
-}
-
-func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *ntor.PublicKey, sessionKey *ntor.Keypair) error {
- if conn.isServer {
- return fmt.Errorf("clientHandshake called on server connection")
- }
-
- // Generate and send the client handshake.
- hs := newClientHandshake(nodeID, peerIdentityKey, sessionKey)
- blob, err := hs.generateHandshake()
- if err != nil {
- return err
- }
- if _, err = conn.Conn.Write(blob); err != nil {
- return err
- }
-
- // Consume the server handshake.
- var hsBuf [maxHandshakeLength]byte
- for {
- n, err := conn.Conn.Read(hsBuf[:])
- if err != nil {
- // The Read() could have returned data and an error, but there is
- // no point in continuing on an EOF or whatever.
- return err
- }
- conn.receiveBuffer.Write(hsBuf[:n])
-
- n, seed, err := hs.parseServerHandshake(conn.receiveBuffer.Bytes())
- if err == ErrMarkNotFoundYet {
- continue
- } else if err != nil {
- return err
- }
- _ = conn.receiveBuffer.Next(n)
-
- // Use the derived key material to intialize the link crypto.
- okm := ntor.Kdf(seed, framing.KeyLength*2)
- conn.encoder = framing.NewEncoder(okm[:framing.KeyLength])
- conn.decoder = framing.NewDecoder(okm[framing.KeyLength:])
-
- return nil
- }
-}
-
-func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.Keypair) error {
- if !conn.isServer {
- return fmt.Errorf("serverHandshake called on client connection")
- }
-
- // Generate the server handshake, and arm the base timeout.
- hs := newServerHandshake(sf.nodeID, sf.identityKey, sessionKey)
- if err := conn.Conn.SetDeadline(time.Now().Add(serverHandshakeTimeout)); err != nil {
- return err
- }
-
- // Consume the client handshake.
- var hsBuf [maxHandshakeLength]byte
- for {
- n, err := conn.Conn.Read(hsBuf[:])
- if err != nil {
- // The Read() could have returned data and an error, but there is
- // no point in continuing on an EOF or whatever.
- return err
- }
- conn.receiveBuffer.Write(hsBuf[:n])
-
- seed, err := hs.parseClientHandshake(sf.replayFilter, conn.receiveBuffer.Bytes())
- if err == ErrMarkNotFoundYet {
- continue
- } else if err != nil {
- return err
- }
- conn.receiveBuffer.Reset()
-
- if err := conn.Conn.SetDeadline(time.Time{}); err != nil {
- return nil
- }
-
- // Use the derived key material to intialize the link crypto.
- okm := ntor.Kdf(seed, framing.KeyLength*2)
- conn.encoder = framing.NewEncoder(okm[framing.KeyLength:])
- conn.decoder = framing.NewDecoder(okm[:framing.KeyLength])
-
- break
- }
-
- // Since the current and only implementation always sends a PRNG seed for
- // the length obfuscation, this makes the amount of data received from the
- // server inconsistent with the length sent from the client.
- //
- // Rebalance this by tweaking the client mimimum padding/server maximum
- // padding, and sending the PRNG seed unpadded (As in, treat the PRNG seed
- // as part of the server response). See inlineSeedFrameLength in
- // handshake_ntor.go.
-
- // Generate/send the response.
- blob, err := hs.generateHandshake()
- if err != nil {
- return err
- }
- var frameBuf bytes.Buffer
- if _, err = frameBuf.Write(blob); err != nil {
- return err
- }
-
- // Send the PRNG seed as the first packet.
- if err := conn.makePacket(&frameBuf, packetTypePrngSeed, sf.lenSeed.Bytes()[:], 0); err != nil {
- return err
- }
- if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
- return err
- }
-
- return nil
-}
-
-func (conn *obfs4Conn) Read(b []byte) (n int, err error) {
- // If there is no payload from the previous Read() calls, consume data off
- // the network. Not all data received is guaranteed to be usable payload,
- // so do this in a loop till data is present or an error occurs.
- for conn.receiveDecodedBuffer.Len() == 0 {
- err = conn.readPackets()
- if err == framing.ErrAgain {
- // Don't proagate this back up the call stack if we happen to break
- // out of the loop.
- err = nil
- continue
- } else if err != nil {
- break
- }
- }
-
- // Even if err is set, attempt to do the read anyway so that all decoded
- // data gets relayed before the connection is torn down.
- if conn.receiveDecodedBuffer.Len() > 0 {
- var berr error
- n, berr = conn.receiveDecodedBuffer.Read(b)
- if err == nil {
- // Only propagate berr if there are not more important (fatal)
- // errors from the network/crypto/packet processing.
- err = berr
- }
- }
-
- return
-}
-
-func (conn *obfs4Conn) Write(b []byte) (n int, err error) {
- chopBuf := bytes.NewBuffer(b)
- var payload [maxPacketPayloadLength]byte
- var frameBuf bytes.Buffer
-
- // Chop the pending data into payload frames.
- for chopBuf.Len() > 0 {
- // Send maximum sized frames.
- rdLen := 0
- rdLen, err = chopBuf.Read(payload[:])
- if err != nil {
- return 0, err
- } else if rdLen == 0 {
- panic(fmt.Sprintf("BUG: Write(), chopping length was 0"))
- }
- n += rdLen
-
- err = conn.makePacket(&frameBuf, packetTypePayload, payload[:rdLen], 0)
- if err != nil {
- return 0, err
- }
- }
-
- if conn.iatMode != iatParanoid {
- // For non-paranoid IAT, pad once per burst. Paranoid IAT handles
- // things differently.
- if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil {
- return 0, err
- }
- }
-
- // Write the pending data onto the network. Partial writes are fatal,
- // because the frame encoder state is advanced, and the code doesn't keep
- // frameBuf around. In theory, write timeouts and whatnot could be
- // supported if this wasn't the case, but that complicates the code.
- if conn.iatMode != iatNone {
- var iatFrame [framing.MaximumSegmentLength]byte
- for frameBuf.Len() > 0 {
- iatWrLen := 0
-
- switch conn.iatMode {
- case iatEnabled:
- // Standard (ScrambleSuit-style) IAT obfuscation optimizes for
- // bulk transport and will write ~MTU sized frames when
- // possible.
- iatWrLen, err = frameBuf.Read(iatFrame[:])
-
- case iatParanoid:
- // Paranoid IAT obfuscation throws performance out of the
- // window and will sample the length distribution every time a
- // write is scheduled.
- targetLen := conn.lenDist.Sample()
- if frameBuf.Len() < targetLen {
- // There's not enough data buffered for the target write,
- // so padding must be inserted.
- if err = conn.padBurst(&frameBuf, targetLen); err != nil {
- return 0, err
- }
- if frameBuf.Len() != targetLen {
- // Ugh, padding came out to a value that required more
- // than one frame, this is relatively unlikely so just
- // resample since there's enough data to ensure that
- // the next sample will be written.
- continue
- }
- }
- iatWrLen, err = frameBuf.Read(iatFrame[:targetLen])
- }
- if err != nil {
- return 0, err
- } else if iatWrLen == 0 {
- panic(fmt.Sprintf("BUG: Write(), iat length was 0"))
- }
-
- // Calculate the delay. The delay resolution is 100 usec, leading
- // to a maximum delay of 10 msec.
- iatDelta := time.Duration(conn.iatDist.Sample() * 100)
-
- // Write then sleep.
- _, err = conn.Conn.Write(iatFrame[:iatWrLen])
- if err != nil {
- return 0, err
- }
- time.Sleep(iatDelta * time.Microsecond)
- }
- } else {
- _, err = conn.Conn.Write(frameBuf.Bytes())
- }
-
- return
-}
-
-func (conn *obfs4Conn) SetDeadline(t time.Time) error {
- return syscall.ENOTSUP
-}
-
-func (conn *obfs4Conn) SetWriteDeadline(t time.Time) error {
- return syscall.ENOTSUP
-}
-
-func (conn *obfs4Conn) closeAfterDelay(sf *obfs4ServerFactory, startTime time.Time) {
- // I-it's not like I w-wanna handshake with you or anything. B-b-baka!
- defer conn.Conn.Close()
-
- delay := time.Duration(sf.closeDelay)*time.Second + serverHandshakeTimeout
- deadline := startTime.Add(delay)
- if time.Now().After(deadline) {
- return
- }
-
- if err := conn.Conn.SetReadDeadline(deadline); err != nil {
- return
- }
-
- // Consume and discard data on this connection until either the specified
- // interval passes or a certain size has been reached.
- discarded := 0
- var buf [framing.MaximumSegmentLength]byte
- for discarded < int(sf.closeDelayBytes) {
- n, err := conn.Conn.Read(buf[:])
- if err != nil {
- return
- }
- discarded += n
- }
-}
-
-func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) {
- tailLen := burst.Len() % framing.MaximumSegmentLength
-
- padLen := 0
- if toPadTo >= tailLen {
- padLen = toPadTo - tailLen
- } else {
- padLen = (framing.MaximumSegmentLength - tailLen) + toPadTo
- }
-
- if padLen > headerLength {
- err = conn.makePacket(burst, packetTypePayload, []byte{},
- uint16(padLen-headerLength))
- if err != nil {
- return
- }
- } else if padLen > 0 {
- err = conn.makePacket(burst, packetTypePayload, []byte{},
- maxPacketPayloadLength)
- if err != nil {
- return
- }
- err = conn.makePacket(burst, packetTypePayload, []byte{},
- uint16(padLen))
- if err != nil {
- return
- }
- }
-
- return
-}
-
-func init() {
- flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation")
-}
-
-var _ base.ClientFactory = (*obfs4ClientFactory)(nil)
-var _ base.ServerFactory = (*obfs4ServerFactory)(nil)
-var _ base.Transport = (*Transport)(nil)
-var _ net.Conn = (*obfs4Conn)(nil)
diff --git a/transports/obfs4/packet.go b/transports/obfs4/packet.go
deleted file mode 100644
index ac3028a..0000000
--- a/transports/obfs4/packet.go
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-package obfs4
-
-import (
- "crypto/sha256"
- "encoding/binary"
- "fmt"
- "io"
-
- "github.com/OperatorFoundation/obfs4/common/drbg"
- "github.com/OperatorFoundation/obfs4/transports/obfs4/framing"
-)
-
-const (
- packetOverhead = 2 + 1
- maxPacketPayloadLength = framing.MaximumFramePayloadLength - packetOverhead
- maxPacketPaddingLength = maxPacketPayloadLength
- seedPacketPayloadLength = seedLength
-
- consumeReadSize = framing.MaximumSegmentLength * 16
-)
-
-const (
- packetTypePayload = iota
- packetTypePrngSeed
-)
-
-// InvalidPacketLengthError is the error returned when decodePacket detects a
-// invalid packet length/
-type InvalidPacketLengthError int
-
-func (e InvalidPacketLengthError) Error() string {
- return fmt.Sprintf("packet: Invalid packet length: %d", int(e))
-}
-
-// InvalidPayloadLengthError is the error returned when decodePacket rejects the
-// payload length.
-type InvalidPayloadLengthError int
-
-func (e InvalidPayloadLengthError) Error() string {
- return fmt.Sprintf("packet: Invalid payload length: %d", int(e))
-}
-
-var zeroPadBytes [maxPacketPaddingLength]byte
-
-func (conn *obfs4Conn) makePacket(w io.Writer, pktType uint8, data []byte, padLen uint16) error {
- var pkt [framing.MaximumFramePayloadLength]byte
-
- if len(data)+int(padLen) > maxPacketPayloadLength {
- panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPacketPayloadLength: %d + %d > %d",
- len(data), padLen, maxPacketPayloadLength))
- }
-
- // Packets are:
- // uint8_t type packetTypePayload (0x00)
- // uint16_t length Length of the payload (Big Endian).
- // uint8_t[] payload Data payload.
- // uint8_t[] padding Padding.
- pkt[0] = pktType
- binary.BigEndian.PutUint16(pkt[1:], uint16(len(data)))
- if len(data) > 0 {
- copy(pkt[3:], data[:])
- }
- copy(pkt[3+len(data):], zeroPadBytes[:padLen])
-
- pktLen := packetOverhead + len(data) + int(padLen)
-
- // Encode the packet in an AEAD frame.
- var frame [framing.MaximumSegmentLength]byte
- frameLen, err := conn.encoder.Encode(frame[:], pkt[:pktLen])
- if err != nil {
- // All encoder errors are fatal.
- return err
- }
- wrLen, err := w.Write(frame[:frameLen])
- if err != nil {
- return err
- } else if wrLen < frameLen {
- return io.ErrShortWrite
- }
-
- return nil
-}
-
-func (conn *obfs4Conn) readPackets() (err error) {
- // Attempt to read off the network.
- var buf [consumeReadSize]byte
- rdLen, rdErr := conn.Conn.Read(buf[:])
- conn.receiveBuffer.Write(buf[:rdLen])
-
- var decoded [framing.MaximumFramePayloadLength]byte
- for conn.receiveBuffer.Len() > 0 {
- // Decrypt an AEAD frame.
- decLen := 0
- decLen, err = conn.decoder.Decode(decoded[:], conn.receiveBuffer)
- if err == framing.ErrAgain {
- break
- } else if err != nil {
- break
- } else if decLen < packetOverhead {
- err = InvalidPacketLengthError(decLen)
- break
- }
-
- // Decode the packet.
- pkt := decoded[0:decLen]
- pktType := pkt[0]
- payloadLen := binary.BigEndian.Uint16(pkt[1:])
- if int(payloadLen) > len(pkt)-packetOverhead {
- err = InvalidPayloadLengthError(int(payloadLen))
- break
- }
- payload := pkt[3 : 3+payloadLen]
-
- switch pktType {
- case packetTypePayload:
- if payloadLen > 0 {
- conn.receiveDecodedBuffer.Write(payload)
- }
- case packetTypePrngSeed:
- // Only regenerate the distribution if we are the client.
- if len(payload) == seedPacketPayloadLength && !conn.isServer {
- var seed *drbg.Seed
- seed, err = drbg.SeedFromBytes(payload)
- if err != nil {
- break
- }
- conn.lenDist.Reset(seed)
- if conn.iatDist != nil {
- iatSeedSrc := sha256.Sum256(seed.Bytes()[:])
- iatSeed, err := drbg.SeedFromBytes(iatSeedSrc[:])
- if err != nil {
- break
- }
- conn.iatDist.Reset(iatSeed)
- }
- }
- default:
- // Ignore unknown packet types.
- }
- }
-
- // Read errors (all fatal) take priority over various frame processing
- // errors.
- if rdErr != nil {
- return rdErr
- }
-
- return
-}
diff --git a/transports/obfs4/statefile.go b/transports/obfs4/statefile.go
deleted file mode 100644
index 6f89c6c..0000000
--- a/transports/obfs4/statefile.go
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * * Redistributions of source code must retain the above copyright notice,
- * this list of conditions and the following disclaimer.
- *
- * * Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
- * POSSIBILITY OF SUCH DAMAGE.
- */
-
-package obfs4
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "os"
- "path"
- "strconv"
- "strings"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/common/drbg"
- "github.com/OperatorFoundation/obfs4/common/ntor"
-)
-
-const (
- stateFile = "obfs4_state.json"
- bridgeFile = "obfs4_bridgeline.txt"
-
- certSuffix = "=="
- certLength = ntor.NodeIDLength + ntor.PublicKeyLength
-)
-
-type jsonServerState struct {
- NodeID string `json:"node-id"`
- PrivateKey string `json:"private-key"`
- PublicKey string `json:"public-key"`
- DrbgSeed string `json:"drbg-seed"`
- IATMode int `json:"iat-mode"`
-}
-
-type obfs4ServerCert struct {
- raw []byte
-}
-
-func (cert *obfs4ServerCert) String() string {
- return strings.TrimSuffix(base64.StdEncoding.EncodeToString(cert.raw), certSuffix)
-}
-
-func (cert *obfs4ServerCert) unpack() (*ntor.NodeID, *ntor.PublicKey) {
- if len(cert.raw) != certLength {
- panic(fmt.Sprintf("cert length %d is invalid", len(cert.raw)))
- }
-
- nodeID, _ := ntor.NewNodeID(cert.raw[:ntor.NodeIDLength])
- pubKey, _ := ntor.NewPublicKey(cert.raw[ntor.NodeIDLength:])
-
- return nodeID, pubKey
-}
-
-func serverCertFromString(encoded string) (*obfs4ServerCert, error) {
- decoded, err := base64.StdEncoding.DecodeString(encoded + certSuffix)
- if err != nil {
- return nil, fmt.Errorf("failed to decode cert: %s", err)
- }
-
- if len(decoded) != certLength {
- return nil, fmt.Errorf("cert length %d is invalid", len(decoded))
- }
-
- return &obfs4ServerCert{raw: decoded}, nil
-}
-
-func serverCertFromState(st *obfs4ServerState) *obfs4ServerCert {
- cert := new(obfs4ServerCert)
- cert.raw = append(st.nodeID.Bytes()[:], st.identityKey.Public().Bytes()[:]...)
- return cert
-}
-
-type obfs4ServerState struct {
- nodeID *ntor.NodeID
- identityKey *ntor.Keypair
- drbgSeed *drbg.Seed
- iatMode int
-
- cert *obfs4ServerCert
-}
-
-func (st *obfs4ServerState) clientString() string {
- return fmt.Sprintf("%s=%s %s=%d", certArg, st.cert, iatArg, st.iatMode)
-}
-
-func serverStateFromArgs(stateDir string, args *pt.Args) (*obfs4ServerState, error) {
- var js jsonServerState
- var nodeIDOk, privKeyOk, seedOk bool
-
- js.NodeID, nodeIDOk = args.Get(nodeIDArg)
- js.PrivateKey, privKeyOk = args.Get(privateKeyArg)
- js.DrbgSeed, seedOk = args.Get(seedArg)
- iatStr, iatOk := args.Get(iatArg)
-
- if !privKeyOk && !nodeIDOk && !seedOk && !iatOk {
- if err := jsonServerStateFromFile(stateDir, &js); err != nil {
- return nil, err
- }
- } else if !privKeyOk {
- return nil, fmt.Errorf("missing argument '%s'", privateKeyArg)
- } else if !nodeIDOk {
- return nil, fmt.Errorf("missing argument '%s'", nodeIDArg)
- } else if !seedOk {
- return nil, fmt.Errorf("missing argument '%s'", seedArg)
- } else if !iatOk {
- // Disable IAT if not specified.
- return nil, fmt.Errorf("missing argument '%s'", iatArg)
- } else {
- // Parse and validate the iat-mode argument.
- iatMode, err := strconv.Atoi(iatStr)
- if err != nil {
- return nil, fmt.Errorf("malformed iat-mode '%s'", iatStr)
- }
- js.IATMode = iatMode
- }
-
- return serverStateFromJSONServerState(stateDir, &js)
-}
-
-func serverStateFromJSONServerState(stateDir string, js *jsonServerState) (*obfs4ServerState, error) {
- var err error
-
- st := new(obfs4ServerState)
- if st.nodeID, err = ntor.NodeIDFromHex(js.NodeID); err != nil {
- return nil, err
- }
- if st.identityKey, err = ntor.KeypairFromHex(js.PrivateKey); err != nil {
- return nil, err
- }
- if st.drbgSeed, err = drbg.SeedFromHex(js.DrbgSeed); err != nil {
- return nil, err
- }
- if js.IATMode < iatNone || js.IATMode > iatParanoid {
- return nil, fmt.Errorf("invalid iat-mode '%d'", js.IATMode)
- }
- st.iatMode = js.IATMode
- st.cert = serverCertFromState(st)
-
- // Generate a human readable summary of the configured endpoint.
- if err = newBridgeFile(stateDir, st); err != nil {
- return nil, err
- }
-
- return st, nil
-}
-
-func jsonServerStateFromFile(stateDir string, js *jsonServerState) error {
- fPath := path.Join(stateDir, stateFile)
- f, err := ioutil.ReadFile(fPath)
- if err != nil {
- if os.IsNotExist(err) {
- if err = newJSONServerState(stateDir, js); err == nil {
- return nil
- }
- }
- return err
- }
-
- if err := json.Unmarshal(f, js); err != nil {
- return fmt.Errorf("failed to load statefile '%s': %s", fPath, err)
- }
-
- return nil
-}
-
-func newJSONServerState(stateDir string, js *jsonServerState) (err error) {
- // Generate everything a server needs, using the cryptographic PRNG.
- var st obfs4ServerState
- rawID := make([]byte, ntor.NodeIDLength)
- if err = csrand.Bytes(rawID); err != nil {
- return
- }
- if st.nodeID, err = ntor.NewNodeID(rawID); err != nil {
- return
- }
- if st.identityKey, err = ntor.NewKeypair(false); err != nil {
- return
- }
- if st.drbgSeed, err = drbg.NewSeed(); err != nil {
- return
- }
- st.iatMode = iatNone
-
- // Encode it into JSON format and write the state file.
- js.NodeID = st.nodeID.Hex()
- js.PrivateKey = st.identityKey.Private().Hex()
- js.PublicKey = st.identityKey.Public().Hex()
- js.DrbgSeed = st.drbgSeed.Hex()
- js.IATMode = st.iatMode
-
- var encoded []byte
- if encoded, err = json.Marshal(js); err != nil {
- return
- }
-
- if err = ioutil.WriteFile(path.Join(stateDir, stateFile), encoded, 0600); err != nil {
- return err
- }
-
- return nil
-}
-
-func newBridgeFile(stateDir string, st *obfs4ServerState) error {
- const prefix = "# obfs4 torrc client bridge line\n" +
- "#\n" +
- "# This file is an automatically generated bridge line based on\n" +
- "# the current obfs4proxy configuration. EDITING IT WILL HAVE\n" +
- "# NO EFFECT.\n" +
- "#\n" +
- "# Before distributing this Bridge, edit the placeholder fields\n" +
- "# to contain the actual values:\n" +
- "# <IP ADDRESS> - The public IP address of your obfs4 bridge.\n" +
- "# <PORT> - The TCP/IP port of your obfs4 bridge.\n" +
- "# <FINGERPRINT> - The bridge's fingerprint.\n\n"
-
- bridgeLine := fmt.Sprintf("Bridge obfs4 <IP ADDRESS>:<PORT> <FINGERPRINT> %s\n",
- st.clientString())
-
- tmp := []byte(prefix + bridgeLine)
- if err := ioutil.WriteFile(path.Join(stateDir, bridgeFile), tmp, 0600); err != nil {
- return err
- }
-
- return nil
-}
diff --git a/transports/scramblesuit/base.go b/transports/scramblesuit/base.go
deleted file mode 100644
index b6f7cb7..0000000
--- a/transports/scramblesuit/base.go
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (c) 2015, 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 scramblesuit provides an implementation of the ScrambleSuit
-// obfuscation protocol. The implementation is client only.
-package scramblesuit
-
-import (
- "fmt"
- "net"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
- "github.com/OperatorFoundation/obfs4/transports/base"
-)
-
-const transportName = "scramblesuit"
-
-// Transport is the ScrambleSuit implementation of the base.Transport interface.
-type Transport struct{}
-
-// Name returns the name of the ScrambleSuit transport protocol.
-func (t *Transport) Name() string {
- return transportName
-}
-
-// ClientFactory returns a new ssClientFactory instance.
-func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) {
- tStore, err := loadTicketStore(stateDir)
- if err != nil {
- return nil, err
- }
- cf := &ssClientFactory{transport: t, ticketStore: tStore}
- return cf, nil
-}
-
-// ServerFactory will one day return a new ssServerFactory instance.
-func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) {
- // TODO: Fill this in eventually, though obfs4 is better.
- return nil, fmt.Errorf("server not supported")
-}
-
-type ssClientFactory struct {
- transport base.Transport
- ticketStore *ssTicketStore
-}
-
-func (cf *ssClientFactory) Transport() base.Transport {
- return cf.transport
-}
-
-func (cf *ssClientFactory) ParseArgs(args *pt.Args) (interface{}, error) {
- return newClientArgs(args)
-}
-
-func (cf *ssClientFactory) Dial(network, addr string, dialFn base.DialFunc, args interface{}) (net.Conn, error) {
- // Validate args before opening outgoing connection.
- ca, ok := args.(*ssClientArgs)
- if !ok {
- return nil, fmt.Errorf("invalid argument type for args")
- }
-
- conn, err := dialFn(network, addr)
- if err != nil {
- return nil, err
- }
- dialConn := conn
- if conn, err = newScrambleSuitClientConn(conn, cf.ticketStore, ca); err != nil {
- dialConn.Close()
- return nil, err
- }
- return conn, nil
-}
-
-var _ base.ClientFactory = (*ssClientFactory)(nil)
-var _ base.Transport = (*Transport)(nil)
diff --git a/transports/scramblesuit/conn.go b/transports/scramblesuit/conn.go
deleted file mode 100644
index 5efd12f..0000000
--- a/transports/scramblesuit/conn.go
+++ /dev/null
@@ -1,523 +0,0 @@
-/*
- * Copyright (c) 2015, 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 scramblesuit
-
-import (
- "bytes"
- "crypto/aes"
- "crypto/cipher"
- "crypto/hmac"
- "crypto/sha256"
- "encoding/base32"
- "encoding/binary"
- "errors"
- "fmt"
- "hash"
- "io"
- "net"
- "time"
-
- "git.torproject.org/pluggable-transports/goptlib.git"
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/common/drbg"
- "github.com/OperatorFoundation/obfs4/common/probdist"
- "github.com/OperatorFoundation/obfs4/common/uniformdh"
-)
-
-const (
- passwordArg = "password"
-
- maxSegmentLength = 1448
- maxPayloadLength = 1427
- sharedSecretLength = 160 / 8 // k_B
- clientHandshakeTimeout = time.Duration(60) * time.Second
-
- minLenDistLength = 21
- maxLenDistLength = maxSegmentLength
-
- keyLength = 32 + 8 + 32
-
- pktPrngSeedLength = 32
- pktOverhead = macLength + pktHdrLength
- pktHdrLength = 2 + 2 + 1
- pktPayload = 1
- pktNewTicket = 1 << 1
- pktPrngSeed = 1 << 2
-)
-
-var (
- // ErrNotSupported is the error returned for a unsupported operation.
- ErrNotSupported = errors.New("scramblesuit: operation not supported")
-
- // ErrInvalidPacket is the error returned when a invalid packet is received.
- ErrInvalidPacket = errors.New("scramblesuit: invalid packet")
-
- zeroPadBytes [maxPayloadLength]byte
-)
-
-type ssSharedSecret [sharedSecretLength]byte
-
-type ssClientArgs struct {
- kB *ssSharedSecret
- sessionKey *uniformdh.PrivateKey
-}
-
-func newClientArgs(args *pt.Args) (ca *ssClientArgs, err error) {
- ca = &ssClientArgs{}
- if ca.kB, err = parsePasswordArg(args); err != nil {
- return nil, err
- }
-
- // Generate the client keypair before opening a connection since the time
- // taken is visible to an adversary. This key might not end up being used
- // if a session ticket is present, but this doesn't take that long.
- if ca.sessionKey, err = uniformdh.GenerateKey(csrand.Reader); err != nil {
- return nil, err
- }
- return
-}
-
-func parsePasswordArg(args *pt.Args) (*ssSharedSecret, error) {
- str, ok := args.Get(passwordArg)
- if !ok {
- return nil, fmt.Errorf("missing argument '%s'", passwordArg)
- }
-
- // To match the obfsproxy behavior, 'str' should contain a Base32 encoded
- // shared secret (k_B) used for handshaking.
- decoded, err := base32.StdEncoding.DecodeString(str)
- if err != nil {
- return nil, fmt.Errorf("failed to decode password: %s", err)
- }
- if len(decoded) != sharedSecretLength {
- return nil, fmt.Errorf("password length %d is invalid", len(decoded))
- }
- ss := new(ssSharedSecret)
- copy(ss[:], decoded)
- return ss, nil
-}
-
-type ssCryptoState struct {
- s cipher.Stream
- mac hash.Hash
-}
-
-func newCryptoState(aesKey []byte, ivPrefix []byte, macKey []byte) (*ssCryptoState, error) {
- // The ScrambleSuit CTR-AES256 link crypto uses an 8 byte prefix from the
- // KDF, and a 64 bit counter initialized to 1 as the IV. The initial value
- // of the counter isn't documented in the spec either.
- var initialCtr = []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
- iv := make([]byte, 0, aes.BlockSize)
- iv = append(iv, ivPrefix...)
- iv = append(iv, initialCtr...)
- b, err := aes.NewCipher(aesKey)
- if err != nil {
- return nil, err
- }
- s := cipher.NewCTR(b, iv)
- mac := hmac.New(sha256.New, macKey)
- return &ssCryptoState{s: s, mac: mac}, nil
-}
-
-type ssConn struct {
- net.Conn
-
- isServer bool
-
- lenDist *probdist.WeightedDist
- receiveBuffer *bytes.Buffer
- receiveDecodedBuffer *bytes.Buffer
- receiveState ssRxState
-
- txCrypto *ssCryptoState
- rxCrypto *ssCryptoState
-
- ticketStore *ssTicketStore
-}
-
-type ssRxState struct {
- mac []byte
- hdr []byte
-
- totalLen int
- payloadLen int
-}
-
-func (conn *ssConn) Read(b []byte) (n int, err error) {
- // If the receive payload buffer is empty, consume data off the network.
- for conn.receiveDecodedBuffer.Len() == 0 {
- if err = conn.readPackets(); err != nil {
- break
- }
- }
-
- // Service the read request using buffered payload.
- if conn.receiveDecodedBuffer.Len() > 0 {
- n, _ = conn.receiveDecodedBuffer.Read(b)
- }
- return
-}
-
-func (conn *ssConn) Write(b []byte) (n int, err error) {
- var frameBuf bytes.Buffer
- p := b
- toSend := len(p)
-
- for toSend > 0 {
- // Send as much payload as will fit into each frame as possible.
- wrLen := len(p)
- if wrLen > maxPayloadLength {
- wrLen = maxPayloadLength
- }
- payload := p[:wrLen]
- if err = conn.makePacket(&frameBuf, pktPayload, payload, 0); err != nil {
- return 0, err
- }
-
- toSend -= wrLen
- p = p[wrLen:]
- n += wrLen
- }
-
- // Pad out the burst as appropriate.
- if err = conn.padBurst(&frameBuf, conn.lenDist.Sample()); err != nil {
- return 0, err
- }
-
- // Write and return.
- _, err = conn.Conn.Write(frameBuf.Bytes())
- return
-}
-
-func (conn *ssConn) SetDeadline(t time.Time) error {
- return ErrNotSupported
-}
-
-func (conn *ssConn) SetReadDeadline(t time.Time) error {
- return ErrNotSupported
-}
-
-func (conn *ssConn) SetWriteDeadline(t time.Time) error {
- return ErrNotSupported
-}
-
-func (conn *ssConn) makePacket(w io.Writer, pktType byte, data []byte, padLen int) error {
- payloadLen := len(data)
- totalLen := payloadLen + padLen
- if totalLen > maxPayloadLength {
- panic(fmt.Sprintf("BUG: makePacket() len(data) + padLen > maxPayloadLength: %d + %d > %d", len(data), padLen, maxPayloadLength))
- }
-
- // Build the packet header (total length, payload length, flags),
- // and append the payload and padding.
- pkt := make([]byte, pktHdrLength, pktHdrLength+payloadLen+padLen)
- binary.BigEndian.PutUint16(pkt[0:], uint16(totalLen))
- binary.BigEndian.PutUint16(pkt[2:], uint16(payloadLen))
- pkt[4] = pktType
- pkt = append(pkt, data...)
- pkt = append(pkt, zeroPadBytes[:padLen]...)
-
- // Encrypt the packet, and calculate the MAC.
- conn.txCrypto.s.XORKeyStream(pkt, pkt)
- conn.txCrypto.mac.Reset()
- conn.txCrypto.mac.Write(pkt)
- mac := conn.txCrypto.mac.Sum(nil)[:macLength]
-
- // Write out MAC | Packet. Note that this does not go onto the network
- // yet, as w is a byte.Buffer (This is done so each call to conn.Write()
- // gets padding added).
- if _, err := w.Write(mac); err != nil {
- return err
- }
- _, err := w.Write(pkt)
- return err
-}
-
-func (conn *ssConn) readPackets() error {
- // Consume and buffer up to 1 MSS worth of data.
- var buf [maxSegmentLength]byte
- rdLen, rdErr := conn.Conn.Read(buf[:])
- conn.receiveBuffer.Write(buf[:rdLen])
-
- // Process incoming packets incrementally. conn.receiveState stores
- // the results of partial processing.
- for conn.receiveBuffer.Len() > 0 {
- if conn.receiveState.mac == nil {
- // Read and store the packet MAC.
- if conn.receiveBuffer.Len() < macLength {
- break
- }
- mac := make([]byte, macLength)
- conn.receiveBuffer.Read(mac)
- conn.receiveState.mac = mac
- }
-
- if conn.receiveState.hdr == nil {
- // Read and store the packet header.
- if conn.receiveBuffer.Len() < pktHdrLength {
- break
- }
- hdr := make([]byte, pktHdrLength)
- conn.receiveBuffer.Read(hdr)
-
- // Add the encrypted packet header to the HMAC instance, and then
- // decrypt it so that the length of the packet can be determined.
- conn.rxCrypto.mac.Reset()
- conn.rxCrypto.mac.Write(hdr)
- conn.rxCrypto.s.XORKeyStream(hdr, hdr)
-
- // Store the plaintext packet header, and host byte order length
- // values.
- totalLen := int(binary.BigEndian.Uint16(hdr[0:]))
- payloadLen := int(binary.BigEndian.Uint16(hdr[2:]))
- if payloadLen > totalLen || totalLen > maxPayloadLength {
- return ErrInvalidPacket
- }
- conn.receiveState.hdr = hdr
- conn.receiveState.totalLen = totalLen
- conn.receiveState.payloadLen = payloadLen
- }
-
- var data []byte
- if conn.receiveState.totalLen > 0 {
- // If the packet actually has payload (including padding), read,
- // digest and decrypt it.
- if conn.receiveBuffer.Len() < conn.receiveState.totalLen {
- break
- }
- data = make([]byte, conn.receiveState.totalLen)
- conn.receiveBuffer.Read(data)
- conn.rxCrypto.mac.Write(data)
- conn.rxCrypto.s.XORKeyStream(data, data)
- }
-
- // Authenticate the packet, by comparing the received MAC with the one
- // calculated over the ciphertext consumed off the network.
- cmpMAC := conn.rxCrypto.mac.Sum(nil)[:macLength]
- if !hmac.Equal(cmpMAC, conn.receiveState.mac[:]) {
- return ErrInvalidPacket
- }
-
- // Based on the packet flags, do something useful with the payload.
- data = data[:conn.receiveState.payloadLen]
- switch conn.receiveState.hdr[4] {
- case pktPayload:
- // User data, write it into the decoded payload buffer so that Read
- // calls can be serviced.
- conn.receiveDecodedBuffer.Write(data)
- case pktNewTicket:
- // New Session Ticket to be used for future handshakes, store it in
- // the Session Ticket store.
- if conn.isServer || len(data) != ticketKeyLength+ticketLength {
- return ErrInvalidPacket
- }
- conn.ticketStore.storeTicket(conn.RemoteAddr(), data)
- case pktPrngSeed:
- // New PRNG_SEED for the protocol polymorphism. Regenerate the
- // length obfuscation probability distribution.
- if conn.isServer || len(data) != pktPrngSeedLength {
- return ErrInvalidPacket
- }
- seed, err := drbg.SeedFromBytes(data)
- if err != nil {
- return ErrInvalidPacket
- }
- conn.lenDist.Reset(seed)
- default:
- return ErrInvalidPacket
- }
-
- // Done processing a packet, clear the partial state.
- conn.receiveState.mac = nil
- conn.receiveState.hdr = nil
- conn.receiveState.totalLen = 0
- conn.receiveState.payloadLen = 0
- }
- return rdErr
-}
-
-func (conn *ssConn) clientHandshake(kB *ssSharedSecret, sessionKey *uniformdh.PrivateKey) error {
- if conn.isServer {
- return fmt.Errorf("clientHandshake called on server connection")
- }
-
- // Query the Session Ticket store to see if there is a stored session
- // ticket.
- ticket, err := conn.ticketStore.getTicket(conn.RemoteAddr())
- if err != nil {
- return err
- } else if ticket != nil {
- // Ok, there is an existing ticket, so attempt to do a Session Ticket
- // handshake. Until we write to the network, failures are non-fatal as
- // we can transition gracefully into doing a UniformDH handshake.
-
- // Derive the keys from the prestored master key received with the
- // ticket. This is done before the actual handshake since the
- // handshake uses the outgoing HMAC-SHA256-128 key for authentication.
- if err = conn.initCrypto(ticket.key[:]); err != nil {
- goto handshakeUDH
- }
-
- // Generate and send the ticket handshake. There is no response, since
- // both sides have the keying material.
- hs := newTicketClientHandshake(conn.txCrypto.mac, ticket)
- blob, err := hs.generateHandshake()
- if err != nil {
- goto handshakeUDH
- }
- if _, err = conn.Conn.Write(blob); err != nil {
- return err
- }
- return nil
- }
-
-handshakeUDH:
- // No session ticket, so take the slow path and do a UniformDH based
- // handshake.
-
- // Generate and send the client handshake.
- hs := newDHClientHandshake(kB, sessionKey)
- blob, err := hs.generateHandshake()
- if err != nil {
- return err
- }
- if _, err = conn.Conn.Write(blob); err != nil {
- return err
- }
-
- // Consume the server handshake. Since we don't actually know the length
- // of the respose, we need to consume data off the network till we either
- // find the tail marker + MAC digest indicating that a handshake response
- // has been received, or the maximum handshake size passes without a valid
- // response.
- var hsBuf [maxHandshakeLength]byte
- for {
- var n int
- if n, err = conn.Conn.Read(hsBuf[:]); err != nil {
- return err
- }
- conn.receiveBuffer.Write(hsBuf[:n])
-
- // Attempt to process all the data seen so far as a response.
- var seed []byte
- n, seed, err = hs.parseServerHandshake(conn.receiveBuffer.Bytes())
- if err == errMarkNotFoundYet {
- // No response found yet, keep trying.
- continue
- } else if err != nil {
- return err
- }
-
- // Ok, done processing the handshake, discard the response, and do the
- // key derivation based off the calculated shared secret.
- _ = conn.receiveBuffer.Next(n)
- err = conn.initCrypto(seed)
- return err
- }
-}
-
-func (conn *ssConn) initCrypto(seed []byte) error {
- // Use HKDF-SHA256 (Expand only, no Extract) to generate session keys from
- // initial keying material.
- okm := hkdfExpand(sha256.New, seed, nil, kdfSecretLength)
- var err error
- conn.txCrypto, err = newCryptoState(okm[0:32], okm[32:40], okm[80:112])
- if err != nil {
- return err
- }
- conn.rxCrypto, err = newCryptoState(okm[40:72], okm[72:80], okm[112:144])
- if err != nil {
- return err
- }
- return nil
-}
-
-func (conn *ssConn) padBurst(burst *bytes.Buffer, sampleLen int) error {
- // Burst contains the fully encrypted+MACed outgoing payload that will be
- // written to the network. Pad it out so that the last segment (based on
- // the ScrambleSuit MTU) is sampleLen bytes.
-
- dataLen := burst.Len() % maxSegmentLength
- padLen := 0
- if sampleLen >= dataLen {
- padLen = sampleLen - dataLen
- } else {
- padLen = (maxSegmentLength - dataLen) + sampleLen
- }
- if padLen < pktOverhead {
- // The padLen is less than the MAC + packet header in length, so
- // two packets are required.
- padLen += maxSegmentLength
- }
-
- if padLen == 0 {
- return nil
- }
- if padLen > maxSegmentLength {
- // Note: packetmorpher.py: getPadding is slightly wrong and only
- // accounts for one of the two packet headers.
- if err := conn.makePacket(burst, pktPayload, nil, 700-pktOverhead); err != nil {
- return err
- }
- return conn.makePacket(burst, pktPayload, nil, padLen-(700+2*pktOverhead))
- }
- return conn.makePacket(burst, pktPayload, nil, padLen-pktOverhead)
-}
-
-func newScrambleSuitClientConn(conn net.Conn, tStore *ssTicketStore, ca *ssClientArgs) (net.Conn, error) {
- // At this point we have kB and our session key, so we can directly
- // start handshaking and seeing what happens.
-
- // Seed the initial polymorphism distribution.
- seed, err := drbg.NewSeed()
- if err != nil {
- return nil, err
- }
- dist := probdist.New(seed, minLenDistLength, maxLenDistLength, true)
-
- // Allocate the client structure.
- c := &ssConn{conn, false, dist, bytes.NewBuffer(nil), bytes.NewBuffer(nil), ssRxState{}, nil, nil, tStore}
-
- // Start the handshake timeout.
- deadline := time.Now().Add(clientHandshakeTimeout)
- if err := conn.SetDeadline(deadline); err != nil {
- return nil, err
- }
-
- // Attempt to handshake.
- if err := c.clientHandshake(ca.kB, ca.sessionKey); err != nil {
- return nil, err
- }
-
- // Stop the handshake timeout.
- if err := conn.SetDeadline(time.Time{}); err != nil {
- return nil, err
- }
-
- return c, nil
-}
diff --git a/transports/scramblesuit/handshake_ticket.go b/transports/scramblesuit/handshake_ticket.go
deleted file mode 100644
index c9b97cc..0000000
--- a/transports/scramblesuit/handshake_ticket.go
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (c) 2015, 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 scramblesuit
-
-import (
- "bytes"
- "encoding/base32"
- "encoding/json"
- "errors"
- "fmt"
- "hash"
- "io/ioutil"
- "net"
- "os"
- "path"
- "strconv"
- "sync"
- "time"
-
- "github.com/OperatorFoundation/obfs4/common/csrand"
-)
-
-const (
- ticketFile = "scramblesuit_tickets.json"
-
- ticketKeyLength = 32
- ticketLength = 112
- ticketLifetime = 60 * 60 * 24 * 7
-
- ticketMinPadLength = 0
- ticketMaxPadLength = 1388
-)
-
-var (
- errInvalidTicket = errors.New("scramblesuit: invalid serialized ticket")
-)
-
-type ssTicketStore struct {
- sync.Mutex
-
- filePath string
- store map[string]*ssTicket
-}
-
-type ssTicket struct {
- key [ticketKeyLength]byte
- ticket [ticketLength]byte
- issuedAt int64
-}
-
-type ssTicketJSON struct {
- KeyTicket string `json:"key-ticket"`
- IssuedAt int64 `json:"issuedAt"`
-}
-
-func (t *ssTicket) isValid() bool {
- return t.issuedAt+ticketLifetime > time.Now().Unix()
-}
-
-func newTicket(raw []byte) (*ssTicket, error) {
- if len(raw) != ticketKeyLength+ticketLength {
- return nil, errInvalidTicket
- }
- t := &ssTicket{issuedAt: time.Now().Unix()}
- copy(t.key[:], raw[0:])
- copy(t.ticket[:], raw[ticketKeyLength:])
- return t, nil
-}
-
-func (s *ssTicketStore) storeTicket(addr net.Addr, rawT []byte) {
- t, err := newTicket(rawT)
- if err != nil {
- // Silently ignore ticket store failures.
- return
- }
-
- s.Lock()
- defer s.Unlock()
-
- // Add the ticket to the map, and checkpoint to disk. Serialization errors
- // are ignored because the handshake code will just use UniformDH if a
- // ticket is not available.
- s.store[addr.String()] = t
- s.serialize()
-}
-
-func (s *ssTicketStore) getTicket(addr net.Addr) (*ssTicket, error) {
- aStr := addr.String()
-
- s.Lock()
- defer s.Unlock()
-
- t, ok := s.store[aStr]
- if ok && t != nil {
- // Tickets are one use only, so remove tickets from the map, and
- // checkpoint the map to disk.
- delete(s.store, aStr)
- err := s.serialize()
- if !t.isValid() {
- // Expired ticket, ignore it.
- return nil, err
- }
- return t, err
- }
-
- // No ticket was found, that's fine.
- return nil, nil
-}
-
-func (s *ssTicketStore) serialize() error {
- encMap := make(map[string]*ssTicketJSON)
- for k, v := range s.store {
- kt := make([]byte, 0, ticketKeyLength+ticketLength)
- kt = append(kt, v.key[:]...)
- kt = append(kt, v.ticket[:]...)
- ktStr := base32.StdEncoding.EncodeToString(kt)
- jsonObj := &ssTicketJSON{KeyTicket: ktStr, IssuedAt: v.issuedAt}
- encMap[k] = jsonObj
- }
- jsonStr, err := json.Marshal(encMap)
- if err != nil {
- return err
- }
- return ioutil.WriteFile(s.filePath, jsonStr, 0600)
-}
-
-func loadTicketStore(stateDir string) (*ssTicketStore, error) {
- fPath := path.Join(stateDir, ticketFile)
- s := &ssTicketStore{filePath: fPath}
- s.store = make(map[string]*ssTicket)
-
- f, err := ioutil.ReadFile(fPath)
- if err != nil {
- // No ticket store is fine.
- if os.IsNotExist(err) {
- return s, nil
- }
-
- // But a file read error is not.
- return nil, err
- }
-
- encMap := make(map[string]*ssTicketJSON)
- if err = json.Unmarshal(f, &encMap); err != nil {
- return nil, fmt.Errorf("failed to load ticket store '%s': '%s'", fPath, err)
- }
- for k, v := range encMap {
- raw, err := base32.StdEncoding.DecodeString(v.KeyTicket)
- if err != nil || len(raw) != ticketKeyLength+ticketLength {
- // Just silently skip corrupted tickets.
- continue
- }
- t := &ssTicket{issuedAt: v.IssuedAt}
- if !t.isValid() {
- // Just ignore expired tickets.
- continue
- }
- copy(t.key[:], raw[0:])
- copy(t.ticket[:], raw[ticketKeyLength:])
- s.store[k] = t
- }
- return s, nil
-}
-
-type ssTicketClientHandshake struct {
- mac hash.Hash
- ticket *ssTicket
- padLen int
-}
-
-func (hs *ssTicketClientHandshake) generateHandshake() ([]byte, error) {
- var buf bytes.Buffer
- hs.mac.Reset()
-
- // The client handshake is T | P | M | MAC(T | P | M | E)
- hs.mac.Write(hs.ticket.ticket[:])
- m := hs.mac.Sum(nil)[:macLength]
- p, err := makePad(hs.padLen)
- if err != nil {
- return nil, err
- }
-
- // Write T, P, M.
- buf.Write(hs.ticket.ticket[:])
- buf.Write(p)
- buf.Write(m)
-
- // Calculate and write the MAC.
- e := []byte(strconv.FormatInt(getEpochHour(), 10))
- hs.mac.Write(p)
- hs.mac.Write(m)
- hs.mac.Write(e)
- buf.Write(hs.mac.Sum(nil)[:macLength])
-
- hs.mac.Reset()
- return buf.Bytes(), nil
-}
-
-func newTicketClientHandshake(mac hash.Hash, ticket *ssTicket) *ssTicketClientHandshake {
- hs := &ssTicketClientHandshake{mac: mac, ticket: ticket}
- hs.padLen = csrand.IntRange(ticketMinPadLength, ticketMaxPadLength)
- return hs
-}
diff --git a/transports/scramblesuit/handshake_uniformdh.go b/transports/scramblesuit/handshake_uniformdh.go
deleted file mode 100644
index 1a577e8..0000000
--- a/transports/scramblesuit/handshake_uniformdh.go
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (c) 2015, 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 scramblesuit
-
-import (
- "bytes"
- "crypto/hmac"
- "crypto/sha256"
- "errors"
- "hash"
- "strconv"
- "time"
-
- "github.com/OperatorFoundation/obfs4/common/csrand"
- "github.com/OperatorFoundation/obfs4/common/uniformdh"
-)
-
-const (
- minHandshakeLength = uniformdh.Size + macLength*2
- maxHandshakeLength = 1532
- dhMinPadLength = 0
- dhMaxPadLength = 1308
- macLength = 128 / 8 // HMAC-SHA256-128()
-
- kdfSecretLength = keyLength * 2
-)
-
-var (
- errMarkNotFoundYet = errors.New("mark not found yet")
-
- // ErrInvalidHandshake is the error returned when the handshake fails.
- ErrInvalidHandshake = errors.New("invalid handshake")
-)
-
-type ssDHClientHandshake struct {
- mac hash.Hash
- keypair *uniformdh.PrivateKey
- epochHour []byte
- padLen int
-
- serverPublicKey *uniformdh.PublicKey
- serverMark []byte
-}
-
-func (hs *ssDHClientHandshake) generateHandshake() ([]byte, error) {
- var buf bytes.Buffer
- hs.mac.Reset()
-
- // The client handshake is X | P_C | M_C | MAC(X | P_C | M_C | E)
- x, err := hs.keypair.PublicKey.Bytes()
- if err != nil {
- return nil, err
- }
- hs.mac.Write(x)
- mC := hs.mac.Sum(nil)[:macLength]
- pC, err := makePad(hs.padLen)
- if err != nil {
- return nil, err
- }
-
- // Write X, P_C, M_C.
- buf.Write(x)
- buf.Write(pC)
- buf.Write(mC)
-
- // Calculate and write the MAC.
- hs.epochHour = []byte(strconv.FormatInt(getEpochHour(), 10))
- hs.mac.Write(pC)
- hs.mac.Write(mC)
- hs.mac.Write(hs.epochHour)
- buf.Write(hs.mac.Sum(nil)[:macLength])
-
- return buf.Bytes(), nil
-}
-
-func (hs *ssDHClientHandshake) parseServerHandshake(resp []byte) (int, []byte, error) {
- if len(resp) < minHandshakeLength {
- return 0, nil, errMarkNotFoundYet
- }
-
- // The server response is Y | P_S | M_S | MAC(Y | P_S | M_S | E).
- if hs.serverPublicKey == nil {
- y := resp[:uniformdh.Size]
-
- // Pull out the public key, and derive the server mark.
- hs.serverPublicKey = &uniformdh.PublicKey{}
- if err := hs.serverPublicKey.SetBytes(y); err != nil {
- return 0, nil, err
- }
- hs.mac.Reset()
- hs.mac.Write(y)
- hs.serverMark = hs.mac.Sum(nil)[:macLength]
- }
-
- // Find the mark+MAC, if it exits.
- endPos := len(resp)
- if endPos > maxHandshakeLength-macLength {
- endPos = maxHandshakeLength - macLength
- }
- pos := bytes.Index(resp[uniformdh.Size:endPos], hs.serverMark)
- if pos == -1 {
- if len(resp) >= maxHandshakeLength {
- // Couldn't find the mark in a maximum length response.
- return 0, nil, ErrInvalidHandshake
- }
- return 0, nil, errMarkNotFoundYet
- } else if len(resp) < pos+2*macLength {
- // Didn't receive the full M_S.
- return 0, nil, errMarkNotFoundYet
- }
- pos += uniformdh.Size
-
- // Validate the MAC.
- hs.mac.Write(resp[uniformdh.Size : pos+macLength])
- hs.mac.Write(hs.epochHour)
- macCmp := hs.mac.Sum(nil)[:macLength]
- macRx := resp[pos+macLength : pos+2*macLength]
- if !hmac.Equal(macCmp, macRx) {
- return 0, nil, ErrInvalidHandshake
- }
-
- // Derive the shared secret.
- ss, err := uniformdh.Handshake(hs.keypair, hs.serverPublicKey)
- if err != nil {
- return 0, nil, err
- }
- seed := sha256.Sum256(ss)
- return pos + 2*macLength, seed[:], nil
-}
-
-func newDHClientHandshake(kB *ssSharedSecret, sessionKey *uniformdh.PrivateKey) *ssDHClientHandshake {
- hs := &ssDHClientHandshake{keypair: sessionKey}
- hs.mac = hmac.New(sha256.New, kB[:])
- hs.padLen = csrand.IntRange(dhMinPadLength, dhMaxPadLength)
- return hs
-}
-
-func getEpochHour() int64 {
- return time.Now().Unix() / 3600
-}
-
-func makePad(padLen int) ([]byte, error) {
- pad := make([]byte, padLen)
- if err := csrand.Bytes(pad); err != nil {
- return nil, err
- }
- return pad, nil
-}
diff --git a/transports/scramblesuit/hkdf_expand.go b/transports/scramblesuit/hkdf_expand.go
deleted file mode 100644
index 9626b38..0000000
--- a/transports/scramblesuit/hkdf_expand.go
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (c) 2015, 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 scramblesuit
-
-import (
- "crypto/hmac"
- "hash"
-)
-
-func hkdfExpand(hashFn func() hash.Hash, prk []byte, info []byte, l int) []byte {
- // Why, yes. golang.org/x/crypto/hkdf exists, and is a fine
- // implementation of HKDF. However it does both the extract
- // and expand, while ScrambleSuit only does extract, with no
- // way to separate the two steps.
-
- h := hmac.New(hashFn, prk)
- digestSz := h.Size()
- if l > 255*digestSz {
- panic("hkdf: requested OKM length > 255*HashLen")
- }
-
- var t []byte
- okm := make([]byte, 0, l)
- toAppend := l
- ctr := byte(1)
- for toAppend > 0 {
- h.Reset()
- h.Write(t)
- h.Write(info)
- h.Write([]byte{ctr})
- t = h.Sum(nil)
- ctr++
-
- aLen := digestSz
- if toAppend < digestSz {
- aLen = toAppend
- }
- okm = append(okm, t[:aLen]...)
- toAppend -= aLen
- }
- return okm
-}