diff options
Diffstat (limited to 'transports/obfs2/obfs2.go')
-rw-r--r-- | transports/obfs2/obfs2.go | 374 |
1 files changed, 0 insertions, 374 deletions
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) |