/* * Copyright (c) 2014, Yawning Angel * 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 "gitlab.com/yawning/obfs4.git/transports/obfs3" import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/sha256" "errors" "io" "net" "time" "git.torproject.org/pluggable-transports/goptlib.git" "gitlab.com/yawning/obfs4.git/common/csrand" "gitlab.com/yawning/obfs4.git/common/uniformdh" "gitlab.com/yawning/obfs4.git/transports/base" ) const ( transportName = "obfs3" clientHandshakeTimeout = time.Duration(30) * time.Second serverHandshakeTimeout = time.Duration(30) * time.Second initiatorKdfString = "Initiator obfuscated data" responderKdfString = "Responder obfuscated data" initiatorMagicString = "Initiator magic" responderMagicString = "Responder magic" maxPadding = 8194 keyLen = 16 ) // Transport is the obfs3 implementation of the base.Transport interface. type Transport struct{} // Name returns the name of the obfs3 transport protocol. func (t *Transport) Name() string { return transportName } // ClientFactory returns a new obfs3ClientFactory instance. func (t *Transport) ClientFactory(stateDir string) (base.ClientFactory, error) { cf := &obfs3ClientFactory{transport: t} return cf, nil } // ServerFactory returns a new obfs3ServerFactory instance. func (t *Transport) ServerFactory(stateDir string, args *pt.Args) (base.ServerFactory, error) { sf := &obfs3ServerFactory{transport: t} return sf, nil } type obfs3ClientFactory struct { transport base.Transport } func (cf *obfs3ClientFactory) Transport() base.Transport { return cf.transport } func (cf *obfs3ClientFactory) ParseArgs(args *pt.Args) (interface{}, error) { return nil, nil } func (cf *obfs3ClientFactory) 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)